-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #56884 - euclio:codeblock-diagnostics, r=QuietMisdreavus
rustdoc: overhaul code block lexing errors Fixes #53919. This PR moves the reporting of code block lexing errors from rendering time to an early pass, so we can use the compiler's error reporting mechanisms. This dramatically improves the diagnostics in this situation: we now de-emphasize the lexing errors as a note under a warning that has a span and suggestion instead of just emitting errors at the top level. Additionally, this PR generalizes the markdown -> source span calculation function, which should allow other rustdoc warnings to use better spans in the future. Last, the PR makes sure that the code block is always emitted in the docs, even if it fails to highlight correctly. Of note: - The new pass unfortunately adds another pass over the docs to gather the doc blocks for syntax-checking. I wonder if this could be combined with the pass that looks for testable blocks? I'm not familiar with that code, so I don't know how feasible that is. - `pulldown_cmark` doesn't make it easy to find the spans of the code blocks, so the code that calculates the spans is a little nasty. It works for all the test cases I threw at it, but I wouldn't be surprised if an edge case would break it. Should have a thorough review. - This PR worsens the state of #56885, since those certain fatal lexing errors are now emitted before docs get generated at all.
- Loading branch information
Showing
10 changed files
with
562 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
use errors::Applicability; | ||
use syntax::parse::lexer::{TokenAndSpan, StringReader as Lexer}; | ||
use syntax::parse::{ParseSess, token}; | ||
use syntax::source_map::FilePathMapping; | ||
use syntax_pos::FileName; | ||
|
||
use clean; | ||
use core::DocContext; | ||
use fold::DocFolder; | ||
use html::markdown::{self, RustCodeBlock}; | ||
use passes::Pass; | ||
|
||
pub const CHECK_CODE_BLOCK_SYNTAX: Pass = | ||
Pass::early("check-code-block-syntax", check_code_block_syntax, | ||
"validates syntax inside Rust code blocks"); | ||
|
||
pub fn check_code_block_syntax(krate: clean::Crate, cx: &DocContext) -> clean::Crate { | ||
SyntaxChecker { cx }.fold_crate(krate) | ||
} | ||
|
||
struct SyntaxChecker<'a, 'tcx: 'a, 'rcx: 'a> { | ||
cx: &'a DocContext<'a, 'tcx, 'rcx>, | ||
} | ||
|
||
impl<'a, 'tcx, 'rcx> SyntaxChecker<'a, 'tcx, 'rcx> { | ||
fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) { | ||
let sess = ParseSess::new(FilePathMapping::empty()); | ||
let source_file = sess.source_map().new_source_file( | ||
FileName::Custom(String::from("doctest")), | ||
dox[code_block.code].to_owned(), | ||
); | ||
|
||
let errors = Lexer::new_or_buffered_errs(&sess, source_file, None).and_then(|mut lexer| { | ||
while let Ok(TokenAndSpan { tok, .. }) = lexer.try_next_token() { | ||
if tok == token::Eof { | ||
break; | ||
} | ||
} | ||
|
||
let errors = lexer.buffer_fatal_errors(); | ||
|
||
if !errors.is_empty() { | ||
Err(errors) | ||
} else { | ||
Ok(()) | ||
} | ||
}); | ||
|
||
if let Err(errors) = errors { | ||
let mut diag = if let Some(sp) = | ||
super::source_span_for_markdown_range(self.cx, &dox, &code_block.range, &item.attrs) | ||
{ | ||
let mut diag = self | ||
.cx | ||
.sess() | ||
.struct_span_warn(sp, "could not parse code block as Rust code"); | ||
|
||
for mut err in errors { | ||
diag.note(&format!("error from rustc: {}", err.message())); | ||
err.cancel(); | ||
} | ||
|
||
if code_block.syntax.is_none() && code_block.is_fenced { | ||
let sp = sp.from_inner_byte_pos(0, 3); | ||
diag.span_suggestion_with_applicability( | ||
sp, | ||
"mark blocks that do not contain Rust code as text", | ||
String::from("```text"), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
|
||
diag | ||
} else { | ||
// We couldn't calculate the span of the markdown block that had the error, so our | ||
// diagnostics are going to be a bit lacking. | ||
let mut diag = self.cx.sess().struct_span_warn( | ||
super::span_of_attrs(&item.attrs), | ||
"doc comment contains an invalid Rust code block", | ||
); | ||
|
||
for mut err in errors { | ||
// Don't bother reporting the error, because we can't show where it happened. | ||
err.cancel(); | ||
} | ||
|
||
if code_block.syntax.is_none() && code_block.is_fenced { | ||
diag.help("mark blocks that do not contain Rust code as text: ```text"); | ||
} | ||
|
||
diag | ||
}; | ||
|
||
diag.emit(); | ||
} | ||
} | ||
} | ||
|
||
impl<'a, 'tcx, 'rcx> DocFolder for SyntaxChecker<'a, 'tcx, 'rcx> { | ||
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | ||
if let Some(dox) = &item.attrs.collapsed_doc_value() { | ||
for code_block in markdown::rust_code_blocks(&dox) { | ||
self.check_rust_syntax(&item, &dox, code_block); | ||
} | ||
} | ||
|
||
self.fold_item_recur(item) | ||
} | ||
} |
Oops, something went wrong.