]>
Commit | Line | Data |
---|---|---|
9fa01778 XL |
1 | use errors::Applicability; |
2 | use syntax::parse::lexer::{TokenAndSpan, StringReader as Lexer}; | |
3 | use syntax::parse::{ParseSess, token}; | |
4 | use syntax::source_map::FilePathMapping; | |
5 | use syntax_pos::FileName; | |
6 | ||
7 | use crate::clean; | |
8 | use crate::core::DocContext; | |
9 | use crate::fold::DocFolder; | |
10 | use crate::html::markdown::{self, RustCodeBlock}; | |
11 | use crate::passes::Pass; | |
12 | ||
13 | pub const CHECK_CODE_BLOCK_SYNTAX: Pass = | |
14 | Pass::early("check-code-block-syntax", check_code_block_syntax, | |
15 | "validates syntax inside Rust code blocks"); | |
16 | ||
17 | pub fn check_code_block_syntax(krate: clean::Crate, cx: &DocContext<'_, '_, '_>) -> clean::Crate { | |
18 | SyntaxChecker { cx }.fold_crate(krate) | |
19 | } | |
20 | ||
21 | struct SyntaxChecker<'a, 'tcx: 'a, 'rcx: 'a> { | |
22 | cx: &'a DocContext<'a, 'tcx, 'rcx>, | |
23 | } | |
24 | ||
25 | impl<'a, 'tcx, 'rcx> SyntaxChecker<'a, 'tcx, 'rcx> { | |
26 | fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) { | |
27 | let sess = ParseSess::new(FilePathMapping::empty()); | |
28 | let source_file = sess.source_map().new_source_file( | |
29 | FileName::Custom(String::from("doctest")), | |
30 | dox[code_block.code].to_owned(), | |
31 | ); | |
32 | ||
33 | let errors = Lexer::new_or_buffered_errs(&sess, source_file, None).and_then(|mut lexer| { | |
34 | while let Ok(TokenAndSpan { tok, .. }) = lexer.try_next_token() { | |
35 | if tok == token::Eof { | |
36 | break; | |
37 | } | |
38 | } | |
39 | ||
40 | let errors = lexer.buffer_fatal_errors(); | |
41 | ||
42 | if !errors.is_empty() { | |
43 | Err(errors) | |
44 | } else { | |
45 | Ok(()) | |
46 | } | |
47 | }); | |
48 | ||
49 | if let Err(errors) = errors { | |
50 | let mut diag = if let Some(sp) = | |
51 | super::source_span_for_markdown_range(self.cx, &dox, &code_block.range, &item.attrs) | |
52 | { | |
53 | let mut diag = self | |
54 | .cx | |
55 | .sess() | |
56 | .struct_span_warn(sp, "could not parse code block as Rust code"); | |
57 | ||
58 | for mut err in errors { | |
59 | diag.note(&format!("error from rustc: {}", err.message())); | |
60 | err.cancel(); | |
61 | } | |
62 | ||
63 | if code_block.syntax.is_none() && code_block.is_fenced { | |
64 | let sp = sp.from_inner_byte_pos(0, 3); | |
65 | diag.span_suggestion( | |
66 | sp, | |
67 | "mark blocks that do not contain Rust code as text", | |
68 | String::from("```text"), | |
69 | Applicability::MachineApplicable, | |
70 | ); | |
71 | } | |
72 | ||
73 | diag | |
74 | } else { | |
75 | // We couldn't calculate the span of the markdown block that had the error, so our | |
76 | // diagnostics are going to be a bit lacking. | |
77 | let mut diag = self.cx.sess().struct_span_warn( | |
78 | super::span_of_attrs(&item.attrs), | |
79 | "doc comment contains an invalid Rust code block", | |
80 | ); | |
81 | ||
82 | for mut err in errors { | |
83 | // Don't bother reporting the error, because we can't show where it happened. | |
84 | err.cancel(); | |
85 | } | |
86 | ||
87 | if code_block.syntax.is_none() && code_block.is_fenced { | |
88 | diag.help("mark blocks that do not contain Rust code as text: ```text"); | |
89 | } | |
90 | ||
91 | diag | |
92 | }; | |
93 | ||
94 | diag.emit(); | |
95 | } | |
96 | } | |
97 | } | |
98 | ||
99 | impl<'a, 'tcx, 'rcx> DocFolder for SyntaxChecker<'a, 'tcx, 'rcx> { | |
100 | fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | |
101 | if let Some(dox) = &item.attrs.collapsed_doc_value() { | |
102 | for code_block in markdown::rust_code_blocks(&dox) { | |
103 | self.check_rust_syntax(&item, &dox, code_block); | |
104 | } | |
105 | } | |
106 | ||
107 | self.fold_item_recur(item) | |
108 | } | |
109 | } |