]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/check_code_block_syntax.rs
Use empty Cache for methods requiring it when filling Cache itself
[rust.git] / src / librustdoc / passes / check_code_block_syntax.rs
1 use rustc_data_structures::sync::{Lock, Lrc};
2 use rustc_errors::{emitter::Emitter, Applicability, Diagnostic, Handler};
3 use rustc_parse::parse_stream_from_source_str;
4 use rustc_session::parse::ParseSess;
5 use rustc_span::source_map::{FilePathMapping, SourceMap};
6 use rustc_span::{FileName, InnerSpan};
7
8 use crate::clean;
9 use crate::core::DocContext;
10 use crate::fold::DocFolder;
11 use crate::html::markdown::{self, RustCodeBlock};
12 use crate::passes::{span_of_attrs, Pass};
13
14 crate const CHECK_CODE_BLOCK_SYNTAX: Pass = Pass {
15     name: "check-code-block-syntax",
16     run: check_code_block_syntax,
17     description: "validates syntax inside Rust code blocks",
18 };
19
20 crate fn check_code_block_syntax(krate: clean::Crate, cx: &DocContext<'_>) -> clean::Crate {
21     SyntaxChecker { cx }.fold_crate(krate)
22 }
23
24 struct SyntaxChecker<'a, 'tcx> {
25     cx: &'a DocContext<'tcx>,
26 }
27
28 impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
29     fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) {
30         let buffer = Lrc::new(Lock::new(Buffer::default()));
31         let emitter = BufferEmitter { buffer: Lrc::clone(&buffer) };
32
33         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
34         let handler = Handler::with_emitter(false, None, Box::new(emitter));
35         let source = dox[code_block.code].to_owned();
36         let sess = ParseSess::with_span_handler(handler, sm);
37
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()
46         })
47         .unwrap_or(false);
48         let buffer = buffer.borrow();
49
50         if buffer.has_errors || is_empty {
51             let mut diag = if let Some(sp) =
52                 super::source_span_for_markdown_range(self.cx, &dox, &code_block.range, &item.attrs)
53             {
54                 let (warning_message, suggest_using_text) = if buffer.has_errors {
55                     ("could not parse code block as Rust code", true)
56                 } else {
57                     ("Rust code block is empty", false)
58                 };
59
60                 let mut diag = self.cx.sess().struct_span_warn(sp, warning_message);
61
62                 if code_block.syntax.is_none() && code_block.is_fenced {
63                     let sp = sp.from_inner(InnerSpan::new(0, 3));
64                     diag.span_suggestion(
65                         sp,
66                         "mark blocks that do not contain Rust code as text",
67                         String::from("```text"),
68                         Applicability::MachineApplicable,
69                     );
70                 } else if suggest_using_text && code_block.is_ignore {
71                     let sp = sp.from_inner(InnerSpan::new(0, 3));
72                     diag.span_suggestion(
73                         sp,
74                         "`ignore` code blocks require valid Rust code for syntax highlighting. \
75                          Mark blocks that do not contain Rust code as text",
76                         String::from("```text,"),
77                         Applicability::MachineApplicable,
78                     );
79                 }
80
81                 diag
82             } else {
83                 // We couldn't calculate the span of the markdown block that had the error, so our
84                 // diagnostics are going to be a bit lacking.
85                 let mut diag = self.cx.sess().struct_span_warn(
86                     super::span_of_attrs(&item.attrs).unwrap_or(item.source.span()),
87                     "doc comment contains an invalid Rust code block",
88                 );
89
90                 if code_block.syntax.is_none() && code_block.is_fenced {
91                     diag.help("mark blocks that do not contain Rust code as text: ```text");
92                 }
93
94                 diag
95             };
96
97             // FIXME(#67563): Provide more context for these errors by displaying the spans inline.
98             for message in buffer.messages.iter() {
99                 diag.note(&message);
100             }
101
102             diag.emit();
103         }
104     }
105 }
106
107 impl<'a, 'tcx> DocFolder for SyntaxChecker<'a, 'tcx> {
108     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
109         if let Some(dox) = &item.attrs.collapsed_doc_value() {
110             let sp = span_of_attrs(&item.attrs).unwrap_or(item.source.span());
111             let extra = crate::html::markdown::ExtraInfo::new_did(self.cx.tcx, item.def_id, sp);
112             for code_block in markdown::rust_code_blocks(&dox, &extra) {
113                 self.check_rust_syntax(&item, &dox, code_block);
114             }
115         }
116
117         Some(self.fold_item_recur(item))
118     }
119 }
120
121 #[derive(Default)]
122 struct Buffer {
123     messages: Vec<String>,
124     has_errors: bool,
125 }
126
127 struct BufferEmitter {
128     buffer: Lrc<Lock<Buffer>>,
129 }
130
131 impl Emitter for BufferEmitter {
132     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
133         let mut buffer = self.buffer.borrow_mut();
134         buffer.messages.push(format!("error from rustc: {}", diag.message[0].0));
135         if diag.is_error() {
136             buffer.has_errors = true;
137         }
138     }
139
140     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
141         None
142     }
143 }