use errors::{Error, ErrorKind};
use serde_json;
use std::str::FromStr;
use std::path::Path;
use runtest::ProcRes;
#[derive(Deserialize)]
struct Diagnostic {
message: String,
code: Option<DiagnosticCode>,
level: String,
spans: Vec<DiagnosticSpan>,
children: Vec<Diagnostic>,
}
#[derive(Deserialize, Clone)]
struct DiagnosticSpan {
file_name: String,
line_start: usize,
line_end: usize,
column_start: usize,
column_end: usize,
is_primary: bool,
label: Option<String>,
suggested_replacement: Option<String>,
expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
}
#[derive(Deserialize, Clone)]
struct DiagnosticSpanMacroExpansion {
span: DiagnosticSpan,
macro_decl_name: String,
}
#[derive(Deserialize, Clone)]
struct DiagnosticCode {
code: String,
explanation: Option<String>,
}
pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
output.lines()
.flat_map(|line| parse_line(file_name, line, output, proc_res))
.collect()
}
fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
if line.starts_with('{') {
match serde_json::from_str::<Diagnostic>(line) {
Ok(diagnostic) => {
let mut expected_errors = vec![];
push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
expected_errors
}
Err(error) => {
proc_res.fatal(Some(&format!("failed to decode compiler output as json: \
`{}`\noutput: {}\nline: {}",
error,
line,
output)));
}
}
} else {
vec![]
}
}
fn push_expected_errors(expected_errors: &mut Vec<Error>,
diagnostic: &Diagnostic,
default_spans: &[&DiagnosticSpan],
file_name: &str) {
let spans_in_this_file: Vec<_> = diagnostic.spans
.iter()
.filter(|span| Path::new(&span.file_name) == Path::new(&file_name))
.collect();
let primary_spans: Vec<_> = spans_in_this_file.iter()
.cloned()
.filter(|span| span.is_primary)
.take(1) .collect();
let primary_spans = if primary_spans.is_empty() {
default_spans
} else {
&primary_spans
};
let with_code = |span: &DiagnosticSpan, text: &str| {
match diagnostic.code {
Some(ref code) =>
format!("{}:{}: {}:{}: {} [{}]",
span.line_start, span.column_start,
span.line_end, span.column_end,
text, code.code.clone()),
None =>
format!("{}:{}: {}:{}: {}",
span.line_start, span.column_start,
span.line_end, span.column_end,
text),
}
};
let mut message_lines = diagnostic.message.lines();
if let Some(first_line) = message_lines.next() {
for span in primary_spans {
let msg = with_code(span, first_line);
let kind = ErrorKind::from_str(&diagnostic.level).ok();
expected_errors.push(Error {
line_num: span.line_start,
kind,
msg,
});
}
}
for next_line in message_lines {
for span in primary_spans {
expected_errors.push(Error {
line_num: span.line_start,
kind: None,
msg: with_code(span, next_line),
});
}
}
for span in primary_spans {
if let Some(ref suggested_replacement) = span.suggested_replacement {
for (index, line) in suggested_replacement.lines().enumerate() {
expected_errors.push(Error {
line_num: span.line_start + index,
kind: Some(ErrorKind::Suggestion),
msg: line.to_string(),
});
}
}
}
for span in primary_spans {
for frame in &span.expansion {
push_backtrace(expected_errors, frame, file_name);
}
}
for span in spans_in_this_file.iter()
.filter(|span| span.label.is_some()) {
expected_errors.push(Error {
line_num: span.line_start,
kind: Some(ErrorKind::Note),
msg: span.label.clone().unwrap(),
});
}
for child in &diagnostic.children {
push_expected_errors(expected_errors, child, primary_spans, file_name);
}
}
fn push_backtrace(expected_errors: &mut Vec<Error>,
expansion: &DiagnosticSpanMacroExpansion,
file_name: &str) {
if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
expected_errors.push(Error {
line_num: expansion.span.line_start,
kind: Some(ErrorKind::Note),
msg: format!("in this expansion of {}", expansion.macro_decl_name),
});
}
for previous_expansion in &expansion.span.expansion {
push_backtrace(expected_errors, previous_expansion, file_name);
}
}