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