1 | //! `rustc` and `cargo` diagnostics extractors. |
2 | //! |
3 | //! These parse diagnostics from the respective stderr JSON output using the |
4 | //! data structures defined in [`cargo_metadata::diagnostic`]. |
5 | |
6 | use super::{Diagnostics, Level, Message}; |
7 | use bstr::ByteSlice; |
8 | use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel, DiagnosticSpan}; |
9 | use regex::Regex; |
10 | use std::{ |
11 | path::{Path, PathBuf}, |
12 | sync::OnceLock, |
13 | }; |
14 | |
15 | fn diag_line(diag: &Diagnostic, file: &Path) -> Option<(spanned::Span, usize)> { |
16 | let span: impl Fn(bool) -> Option<(…)> = |primary: bool| { |
17 | diagIter<'_, DiagnosticSpan>.spans |
18 | .iter() |
19 | .find_map(|span: &DiagnosticSpan| span_line(span, file, primary)) |
20 | }; |
21 | span(primary:true).or_else(|| span(primary:false)) |
22 | } |
23 | |
24 | /// Put the message and its children into the line-indexed list. |
25 | fn insert_recursive( |
26 | diag: Diagnostic, |
27 | file: &Path, |
28 | messages: &mut Vec<Vec<Message>>, |
29 | messages_from_unknown_file_or_line: &mut Vec<Message>, |
30 | line: Option<(spanned::Span, usize)>, |
31 | ) { |
32 | let line = diag_line(&diag, file).or(line); |
33 | let msg = Message { |
34 | level: diag.level.into(), |
35 | message: diag.message, |
36 | line: line.as_ref().map(|&(_, l)| l), |
37 | span: line.as_ref().map(|(s, _)| s.clone()), |
38 | code: diag.code.map(|x| x.code), |
39 | }; |
40 | if let Some((_, line)) = line.clone() { |
41 | if messages.len() <= line { |
42 | messages.resize_with(line + 1, Vec::new); |
43 | } |
44 | messages[line].push(msg); |
45 | // All other messages go into the general bin, unless they are specifically of the |
46 | // "aborting due to X previous errors" variety, as we never want to match those. They |
47 | // only count the number of errors and provide no useful information about the tests. |
48 | } else if !(msg.message.starts_with("aborting due to" ) |
49 | && msg.message.contains("previous error" )) |
50 | { |
51 | messages_from_unknown_file_or_line.push(msg); |
52 | } |
53 | for child in diag.children { |
54 | insert_recursive( |
55 | child, |
56 | file, |
57 | messages, |
58 | messages_from_unknown_file_or_line, |
59 | line.clone(), |
60 | ) |
61 | } |
62 | } |
63 | |
64 | /// Returns the most expanded line number *in the given file*, if possible. |
65 | fn span_line(span: &DiagnosticSpan, file: &Path, primary: bool) -> Option<(spanned::Span, usize)> { |
66 | let file_name = PathBuf::from(&span.file_name); |
67 | if let Some(exp) = &span.expansion { |
68 | if let Some(line) = span_line(&exp.span, file, !primary || span.is_primary) { |
69 | return Some(line); |
70 | } else if file_name != file { |
71 | return if !primary && span.is_primary { |
72 | span_line(&exp.span, file, false) |
73 | } else { |
74 | None |
75 | }; |
76 | } |
77 | } |
78 | ((!primary || span.is_primary) && file_name == file).then(|| { |
79 | let span = || { |
80 | Some(( |
81 | spanned::Span { |
82 | file: file_name, |
83 | bytes: usize::try_from(span.byte_start).unwrap() |
84 | ..usize::try_from(span.byte_end).unwrap(), |
85 | }, |
86 | span.line_start, |
87 | )) |
88 | }; |
89 | span().unwrap_or_default() |
90 | }) |
91 | } |
92 | |
93 | fn filter_annotations_from_rendered(rendered: &str) -> std::borrow::Cow<'_, str> { |
94 | static ANNOTATIONS_RE: OnceLock<Regex> = OnceLock::new(); |
95 | ANNOTATIONS_RE |
96 | .get_or_init(|| Regex::new(r" *//(\[[a-z,]+\])?~.*" ).unwrap()) |
97 | .replace_all(haystack:rendered, rep:"" ) |
98 | } |
99 | |
100 | /// `rustc` diagnostics extractor. |
101 | pub fn rustc_diagnostics_extractor(file: &Path, stderr: &[u8]) -> Diagnostics { |
102 | let mut rendered = Vec::new(); |
103 | let mut messages = vec![]; |
104 | let mut messages_from_unknown_file_or_line = vec![]; |
105 | for line in stderr.lines_with_terminator() { |
106 | if line.starts_with_str(b"{" ) { |
107 | let msg = |
108 | serde_json::from_slice::<cargo_metadata::diagnostic::Diagnostic>(line).unwrap(); |
109 | |
110 | rendered.extend( |
111 | filter_annotations_from_rendered(msg.rendered.as_ref().unwrap()).as_bytes(), |
112 | ); |
113 | insert_recursive( |
114 | msg, |
115 | file, |
116 | &mut messages, |
117 | &mut messages_from_unknown_file_or_line, |
118 | None, |
119 | ); |
120 | } else { |
121 | // FIXME: do we want to throw interpreter stderr into a separate file? |
122 | rendered.extend(line); |
123 | } |
124 | } |
125 | Diagnostics { |
126 | rendered, |
127 | messages, |
128 | messages_from_unknown_file_or_line, |
129 | } |
130 | } |
131 | |
132 | /// `cargo` diagnostics extractor. |
133 | pub fn cargo_diagnostics_extractor(file: &Path, stderr: &[u8]) -> Diagnostics { |
134 | let mut rendered = Vec::new(); |
135 | let mut messages = vec![]; |
136 | let mut messages_from_unknown_file_or_line = vec![]; |
137 | for message in cargo_metadata::Message::parse_stream(stderr) { |
138 | match message.unwrap() { |
139 | cargo_metadata::Message::CompilerMessage(msg) => { |
140 | let msg = msg.message; |
141 | rendered.extend( |
142 | filter_annotations_from_rendered(msg.rendered.as_ref().unwrap()).as_bytes(), |
143 | ); |
144 | insert_recursive( |
145 | msg, |
146 | file, |
147 | &mut messages, |
148 | &mut messages_from_unknown_file_or_line, |
149 | None, |
150 | ); |
151 | } |
152 | cargo_metadata::Message::TextLine(line) => { |
153 | rendered.extend(line.bytes()); |
154 | rendered.push(b' \n' ) |
155 | } |
156 | _ => {} |
157 | } |
158 | } |
159 | Diagnostics { |
160 | rendered, |
161 | messages, |
162 | messages_from_unknown_file_or_line, |
163 | } |
164 | } |
165 | |
166 | impl From<DiagnosticLevel> for Level { |
167 | fn from(value: DiagnosticLevel) -> Self { |
168 | match value { |
169 | DiagnosticLevel::Ice => Level::Ice, |
170 | DiagnosticLevel::Error => Level::Error, |
171 | DiagnosticLevel::Warning => Level::Warn, |
172 | DiagnosticLevel::FailureNote => Level::FailureNote, |
173 | DiagnosticLevel::Note => Level::Note, |
174 | DiagnosticLevel::Help => Level::Help, |
175 | other: DiagnosticLevel => panic!("rustc got a new kind of diagnostic level: {other:?}" ), |
176 | } |
177 | } |
178 | } |
179 | |