]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/passes/check_code_block_syntax.rs
Merge tag 'debian/1.52.1+dfsg1-1_exp2' into proxmox/buster
[rustc.git] / src / librustdoc / passes / check_code_block_syntax.rs
CommitLineData
dfeec247
XL
1use rustc_data_structures::sync::{Lock, Lrc};
2use rustc_errors::{emitter::Emitter, Applicability, Diagnostic, Handler};
1b1a35ee 3use rustc_parse::parse_stream_from_source_str;
74b04a01 4use rustc_session::parse::ParseSess;
dfeec247
XL
5use rustc_span::source_map::{FilePathMapping, SourceMap};
6use rustc_span::{FileName, InnerSpan};
9fa01778
XL
7
8use crate::clean;
9use crate::core::DocContext;
10use crate::fold::DocFolder;
11use crate::html::markdown::{self, RustCodeBlock};
f9f354fc 12use crate::passes::{span_of_attrs, Pass};
9fa01778 13
fc512014 14crate 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 20crate fn check_code_block_syntax(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
9fa01778
XL
21 SyntaxChecker { cx }.fold_crate(krate)
22}
23
dc9dc135 24struct SyntaxChecker<'a, 'tcx> {
532ac7d7 25 cx: &'a DocContext<'tcx>,
9fa01778
XL
26}
27
532ac7d7 28impl<'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 110impl<'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)]
125struct Buffer {
126 messages: Vec<String>,
127 has_errors: bool,
128}
129
dfeec247 130struct BufferEmitter {
1b1a35ee 131 buffer: Lrc<Lock<Buffer>>,
dfeec247
XL
132}
133
134impl 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}