Skip to content

Commit 1baf3ff

Browse files
committed
Avoid StringReader when checking code blocks for syntax errors
`parse_stream_from_source_str` is a more stable API to convert a string into a bunch of tokens, and it also catches errors about mismatched parenthesis.
1 parent 1fd8636 commit 1baf3ff

File tree

1 file changed

+32
-43
lines changed

1 file changed

+32
-43
lines changed

src/librustdoc/passes/check_code_block_syntax.rs

+32-43
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use rustc_ast::token;
21
use rustc_data_structures::sync::{Lock, Lrc};
32
use rustc_errors::{emitter::Emitter, Applicability, Diagnostic, Handler};
4-
use rustc_parse::lexer::StringReader as Lexer;
3+
use rustc_parse::parse_stream_from_source_str;
54
use rustc_session::parse::ParseSess;
65
use rustc_span::source_map::{FilePathMapping, SourceMap};
76
use rustc_span::{FileName, InnerSpan};
@@ -28,49 +27,34 @@ struct SyntaxChecker<'a, 'tcx> {
2827

2928
impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
3029
fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) {
31-
let buffered_messages = Lrc::new(Lock::new(vec![]));
32-
33-
let emitter = BufferEmitter { messages: Lrc::clone(&buffered_messages) };
30+
let buffer = Lrc::new(Lock::new(Buffer::default()));
31+
let emitter = BufferEmitter { buffer: Lrc::clone(&buffer) };
3432

3533
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
3634
let handler = Handler::with_emitter(false, None, Box::new(emitter));
35+
let source = dox[code_block.code].to_owned();
3736
let sess = ParseSess::with_span_handler(handler, sm);
38-
let source_file = sess.source_map().new_source_file(
39-
FileName::Custom(String::from("doctest")),
40-
dox[code_block.code].to_owned(),
41-
);
42-
43-
let validation_status = rustc_driver::catch_fatal_errors(|| {
44-
let mut has_syntax_errors = false;
45-
let mut only_whitespace = true;
46-
// even if there is a syntax error, we need to run the lexer over the whole file
47-
let mut lexer = Lexer::new(&sess, source_file, None);
48-
loop {
49-
match lexer.next_token().kind {
50-
token::Eof => break,
51-
token::Whitespace => (),
52-
token::Unknown(..) => has_syntax_errors = true,
53-
_ => only_whitespace = false,
54-
}
55-
}
5637

57-
if has_syntax_errors {
58-
Some(CodeBlockInvalid::SyntaxError)
59-
} else if only_whitespace {
60-
Some(CodeBlockInvalid::Empty)
61-
} else {
62-
None
63-
}
38+
let is_empty = rustc_driver::catch_fatal_errors(|| {
39+
parse_stream_from_source_str(
40+
FileName::Custom(String::from("doctest")),
41+
source,
42+
&sess,
43+
None,
44+
)
45+
.is_empty()
6446
})
65-
.unwrap_or(Some(CodeBlockInvalid::SyntaxError));
47+
.unwrap_or(false);
48+
let buffer = buffer.borrow();
6649

67-
if let Some(code_block_invalid) = validation_status {
50+
if buffer.has_errors || is_empty {
6851
let mut diag = if let Some(sp) =
6952
super::source_span_for_markdown_range(self.cx, &dox, &code_block.range, &item.attrs)
7053
{
71-
let warning_message = match code_block_invalid {
72-
CodeBlockInvalid::SyntaxError => "could not parse code block as Rust code",
73-
CodeBlockInvalid::Empty => "Rust code block is empty",
54+
let warning_message = if buffer.has_errors {
55+
"could not parse code block as Rust code"
56+
} else {
57+
"Rust code block is empty"
7458
};
7559

7660
let mut diag = self.cx.sess().struct_span_warn(sp, warning_message);
@@ -102,7 +86,7 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
10286
};
10387

10488
// FIXME(#67563): Provide more context for these errors by displaying the spans inline.
105-
for message in buffered_messages.borrow().iter() {
89+
for message in buffer.messages.iter() {
10690
diag.note(&message);
10791
}
10892

@@ -125,21 +109,26 @@ impl<'a, 'tcx> DocFolder for SyntaxChecker<'a, 'tcx> {
125109
}
126110
}
127111

112+
#[derive(Default)]
113+
struct Buffer {
114+
messages: Vec<String>,
115+
has_errors: bool,
116+
}
117+
128118
struct BufferEmitter {
129-
messages: Lrc<Lock<Vec<String>>>,
119+
buffer: Lrc<Lock<Buffer>>,
130120
}
131121

132122
impl Emitter for BufferEmitter {
133123
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
134-
self.messages.borrow_mut().push(format!("error from rustc: {}", diag.message[0].0));
124+
let mut buffer = self.buffer.borrow_mut();
125+
buffer.messages.push(format!("error from rustc: {}", diag.message[0].0));
126+
if diag.is_error() {
127+
buffer.has_errors = true;
128+
}
135129
}
136130

137131
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
138132
None
139133
}
140134
}
141-
142-
enum CodeBlockInvalid {
143-
SyntaxError,
144-
Empty,
145-
}

0 commit comments

Comments
 (0)