|
1 | 1 | use rustc_data_structures::sync::{Lock, Lrc}; |
2 | 2 | use rustc_errors::{emitter::Emitter, Applicability, Diagnostic, Handler}; |
| 3 | +use rustc_middle::lint::LintDiagnosticBuilder; |
3 | 4 | use rustc_parse::parse_stream_from_source_str; |
4 | 5 | use rustc_session::parse::ParseSess; |
5 | 6 | use rustc_span::source_map::{FilePathMapping, SourceMap}; |
@@ -47,63 +48,86 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { |
47 | 48 | .unwrap_or(false); |
48 | 49 | let buffer = buffer.borrow(); |
49 | 50 |
|
50 | | - if buffer.has_errors || is_empty { |
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 | | - ) { |
57 | | - let (warning_message, suggest_using_text) = if buffer.has_errors { |
58 | | - ("could not parse code block as Rust code", true) |
59 | | - } else { |
60 | | - ("Rust code block is empty", false) |
61 | | - }; |
62 | | - |
63 | | - let mut diag = self.cx.sess().struct_span_warn(sp, warning_message); |
64 | | - |
65 | | - if code_block.syntax.is_none() && code_block.is_fenced { |
66 | | - let sp = sp.from_inner(InnerSpan::new(0, 3)); |
67 | | - diag.span_suggestion( |
68 | | - sp, |
69 | | - "mark blocks that do not contain Rust code as text", |
70 | | - String::from("```text"), |
71 | | - Applicability::MachineApplicable, |
| 51 | + if !buffer.has_errors && !is_empty { |
| 52 | + // No errors in a non-empty program. |
| 53 | + return; |
| 54 | + } |
| 55 | + |
| 56 | + let local_id = match item.def_id.as_local() { |
| 57 | + Some(id) => id, |
| 58 | + // We don't need to check the syntax for other crates so returning |
| 59 | + // without doing anything should not be a problem. |
| 60 | + None => return, |
| 61 | + }; |
| 62 | + |
| 63 | + let hir_id = self.cx.tcx.hir().local_def_id_to_hir_id(local_id); |
| 64 | + let empty_block = code_block.syntax.is_none() && code_block.is_fenced; |
| 65 | + let is_ignore = code_block.is_ignore; |
| 66 | + |
| 67 | + // The span and whether it is precise or not. |
| 68 | + let (sp, precise_span) = match super::source_span_for_markdown_range( |
| 69 | + self.cx.tcx, |
| 70 | + &dox, |
| 71 | + &code_block.range, |
| 72 | + &item.attrs, |
| 73 | + ) { |
| 74 | + Some(sp) => (sp, true), |
| 75 | + None => (item.attr_span(self.cx.tcx), false), |
| 76 | + }; |
| 77 | + |
| 78 | + // lambda that will use the lint to start a new diagnostic and add |
| 79 | + // a suggestion to it when needed. |
| 80 | + let diag_builder = |lint: LintDiagnosticBuilder<'_>| { |
| 81 | + let explanation = if is_ignore { |
| 82 | + "`ignore` code blocks require valid Rust code for syntax highlighting; \ |
| 83 | + mark blocks that do not contain Rust code as text" |
| 84 | + } else { |
| 85 | + "mark blocks that do not contain Rust code as text" |
| 86 | + }; |
| 87 | + let msg = if buffer.has_errors { |
| 88 | + "could not parse code block as Rust code" |
| 89 | + } else { |
| 90 | + "Rust code block is empty" |
| 91 | + }; |
| 92 | + let mut diag = lint.build(msg); |
| 93 | + |
| 94 | + if precise_span { |
| 95 | + if is_ignore { |
| 96 | + // giving an accurate suggestion is hard because `ignore` might not have come first in the list. |
| 97 | + // just give a `help` instead. |
| 98 | + diag.span_help( |
| 99 | + sp.from_inner(InnerSpan::new(0, 3)), |
| 100 | + &format!("{}: ```text", explanation), |
72 | 101 | ); |
73 | | - } else if suggest_using_text && code_block.is_ignore { |
74 | | - let sp = sp.from_inner(InnerSpan::new(0, 3)); |
| 102 | + } else if empty_block { |
75 | 103 | 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,"), |
| 104 | + sp.from_inner(InnerSpan::new(0, 3)), |
| 105 | + explanation, |
| 106 | + String::from("```text"), |
80 | 107 | Applicability::MachineApplicable, |
81 | 108 | ); |
82 | 109 | } |
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( |
89 | | - item.attr_span(self.cx.tcx), |
90 | | - "doc comment contains an invalid Rust code block", |
91 | | - ); |
92 | | - |
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 | | - }; |
| 110 | + } else if empty_block || is_ignore { |
| 111 | + diag.help(&format!("{}: ```text", explanation)); |
| 112 | + } |
99 | 113 |
|
100 | 114 | // FIXME(#67563): Provide more context for these errors by displaying the spans inline. |
101 | 115 | for message in buffer.messages.iter() { |
102 | 116 | diag.note(&message); |
103 | 117 | } |
104 | 118 |
|
105 | 119 | diag.emit(); |
106 | | - } |
| 120 | + }; |
| 121 | + |
| 122 | + // Finally build and emit the completed diagnostic. |
| 123 | + // All points of divergence have been handled earlier so this can be |
| 124 | + // done the same way whether the span is precise or not. |
| 125 | + self.cx.tcx.struct_span_lint_hir( |
| 126 | + crate::lint::INVALID_RUST_CODEBLOCKS, |
| 127 | + hir_id, |
| 128 | + sp, |
| 129 | + diag_builder, |
| 130 | + ); |
107 | 131 | } |
108 | 132 | } |
109 | 133 |
|
|
0 commit comments