1 use rustc_data_structures
::sync
::{Lock, Lrc}
;
2 use rustc_errors
::{emitter::Emitter, Applicability, Diagnostic, Handler}
;
3 use rustc_middle
::lint
::LintDiagnosticBuilder
;
4 use rustc_parse
::parse_stream_from_source_str
;
5 use rustc_session
::parse
::ParseSess
;
6 use rustc_span
::source_map
::{FilePathMapping, SourceMap}
;
7 use rustc_span
::{hygiene::AstPass, ExpnData, ExpnKind, FileName, InnerSpan, DUMMY_SP}
;
10 use crate::core
::DocContext
;
11 use crate::fold
::DocFolder
;
12 use crate::html
::markdown
::{self, RustCodeBlock}
;
13 use crate::passes
::Pass
;
15 crate const CHECK_CODE_BLOCK_SYNTAX
: Pass
= Pass
{
16 name
: "check-code-block-syntax",
17 run
: check_code_block_syntax
,
18 description
: "validates syntax inside Rust code blocks",
21 crate fn check_code_block_syntax(krate
: clean
::Crate
, cx
: &mut DocContext
<'_
>) -> clean
::Crate
{
22 SyntaxChecker { cx }
.fold_crate(krate
)
25 struct SyntaxChecker
<'a
, 'tcx
> {
26 cx
: &'a DocContext
<'tcx
>,
29 impl<'a
, 'tcx
> SyntaxChecker
<'a
, 'tcx
> {
30 fn check_rust_syntax(&self, item
: &clean
::Item
, dox
: &str, code_block
: RustCodeBlock
) {
31 let buffer
= Lrc
::new(Lock
::new(Buffer
::default()));
32 let emitter
= BufferEmitter { buffer: Lrc::clone(&buffer) }
;
34 let sm
= Lrc
::new(SourceMap
::new(FilePathMapping
::empty()));
35 let handler
= Handler
::with_emitter(false, None
, Box
::new(emitter
));
36 let source
= dox
[code_block
.code
].to_owned();
37 let sess
= ParseSess
::with_span_handler(handler
, sm
);
39 let edition
= code_block
.lang_string
.edition
.unwrap_or(self.cx
.tcx
.sess
.edition());
40 let expn_data
= ExpnData
::default(
41 ExpnKind
::AstPass(AstPass
::TestHarness
),
47 let span
= DUMMY_SP
.fresh_expansion(expn_data
, self.cx
.tcx
.create_stable_hashing_context());
49 let is_empty
= rustc_driver
::catch_fatal_errors(|| {
50 parse_stream_from_source_str(
51 FileName
::Custom(String
::from("doctest")),
59 let buffer
= buffer
.borrow();
61 if !buffer
.has_errors
&& !is_empty
{
62 // No errors in a non-empty program.
66 let local_id
= match item
.def_id
.as_def_id().and_then(|x
| x
.as_local()) {
68 // We don't need to check the syntax for other crates so returning
69 // without doing anything should not be a problem.
73 let hir_id
= self.cx
.tcx
.hir().local_def_id_to_hir_id(local_id
);
74 let empty_block
= code_block
.lang_string
== Default
::default() && code_block
.is_fenced
;
75 let is_ignore
= code_block
.lang_string
.ignore
!= markdown
::Ignore
::None
;
77 // The span and whether it is precise or not.
78 let (sp
, precise_span
) = match super::source_span_for_markdown_range(
84 Some(sp
) => (sp
, true),
85 None
=> (item
.attr_span(self.cx
.tcx
), false),
88 // lambda that will use the lint to start a new diagnostic and add
89 // a suggestion to it when needed.
90 let diag_builder
= |lint
: LintDiagnosticBuilder
<'_
>| {
91 let explanation
= if is_ignore
{
92 "`ignore` code blocks require valid Rust code for syntax highlighting; \
93 mark blocks that do not contain Rust code as text"
95 "mark blocks that do not contain Rust code as text"
97 let msg
= if buffer
.has_errors
{
98 "could not parse code block as Rust code"
100 "Rust code block is empty"
102 let mut diag
= lint
.build(msg
);
106 // giving an accurate suggestion is hard because `ignore` might not have come first in the list.
107 // just give a `help` instead.
109 sp
.from_inner(InnerSpan
::new(0, 3)),
110 &format
!("{}: ```text", explanation
),
112 } else if empty_block
{
113 diag
.span_suggestion(
114 sp
.from_inner(InnerSpan
::new(0, 3)),
116 String
::from("```text"),
117 Applicability
::MachineApplicable
,
120 } else if empty_block
|| is_ignore
{
121 diag
.help(&format
!("{}: ```text", explanation
));
124 // FIXME(#67563): Provide more context for these errors by displaying the spans inline.
125 for message
in buffer
.messages
.iter() {
132 // Finally build and emit the completed diagnostic.
133 // All points of divergence have been handled earlier so this can be
134 // done the same way whether the span is precise or not.
135 self.cx
.tcx
.struct_span_lint_hir(
136 crate::lint
::INVALID_RUST_CODEBLOCKS
,
144 impl<'a
, 'tcx
> DocFolder
for SyntaxChecker
<'a
, 'tcx
> {
145 fn fold_item(&mut self, item
: clean
::Item
) -> Option
<clean
::Item
> {
146 if let Some(dox
) = &item
.attrs
.collapsed_doc_value() {
147 let sp
= item
.attr_span(self.cx
.tcx
);
148 let extra
= crate::html
::markdown
::ExtraInfo
::new_did(
150 item
.def_id
.expect_def_id(),
153 for code_block
in markdown
::rust_code_blocks(&dox
, &extra
) {
154 self.check_rust_syntax(&item
, &dox
, code_block
);
158 Some(self.fold_item_recur(item
))
164 messages
: Vec
<String
>,
168 struct BufferEmitter
{
169 buffer
: Lrc
<Lock
<Buffer
>>,
172 impl Emitter
for BufferEmitter
{
173 fn emit_diagnostic(&mut self, diag
: &Diagnostic
) {
174 let mut buffer
= self.buffer
.borrow_mut();
175 buffer
.messages
.push(format
!("error from rustc: {}", diag
.message
[0].0));
177 buffer
.has_errors
= true;
181 fn source_map(&self) -> Option
<&Lrc
<SourceMap
>> {