use super::ast::{Diagnostic, ErrorType, Level, Namespace};
use crate::file_resolver::FileResolver;
use crate::standard_json::{LocJson, OutputJson};
use codespan_reporting::{diagnostic, files, term};
use itertools::Itertools;
use solang_parser::pt::Loc;
use std::{
collections::HashMap,
slice::Iter,
{io, sync::Arc},
};
#[derive(Default, Debug)]
pub struct Diagnostics {
contents: Vec<Diagnostic>,
has_error: bool,
}
impl Diagnostics {
pub fn any_errors(&self) -> bool {
self.has_error
}
pub fn len(&self) -> usize {
self.contents.len()
}
pub fn iter(&self) -> Iter<Diagnostic> {
self.contents.iter()
}
pub fn is_empty(&self) -> bool {
self.contents.is_empty()
}
pub fn push(&mut self, diagnostic: Diagnostic) {
if matches!(diagnostic.level, Level::Error) {
self.has_error = true;
}
self.contents.push(diagnostic);
}
pub fn extend(&mut self, diagnostics: Diagnostics) {
self.has_error |= diagnostics.has_error;
self.contents.extend(diagnostics.contents);
}
pub fn extend_non_casting(&mut self, other: &Diagnostics) -> bool {
let others: Vec<_> = other
.iter()
.filter(|diag| diag.ty != ErrorType::CastError)
.cloned()
.collect();
if others.is_empty() {
false
} else {
if !self.has_error {
self.has_error = others.iter().any(|m| m.level == Level::Error);
}
self.contents.extend(others);
true
}
}
pub fn append(&mut self, diagnostics: &mut Vec<Diagnostic>) {
if !self.has_error {
self.has_error = diagnostics.iter().any(|m| m.level == Level::Error);
}
self.contents.append(diagnostics);
}
pub fn first_error(&self) -> String {
match self.contents.iter().find(|m| m.level == Level::Error) {
Some(m) => m.message.to_owned(),
None => panic!("no errors found"),
}
}
pub fn count_warnings(&self) -> usize {
self.contents
.iter()
.filter(|&x| x.level == Level::Warning)
.count()
}
pub fn first_warning(&self) -> &Diagnostic {
self.contents
.iter()
.find_or_first(|&x| x.level == Level::Warning)
.unwrap()
}
pub fn warnings(&self) -> Vec<&Diagnostic> {
let mut res = Vec::new();
for elem in &self.contents {
if elem.level == Level::Warning {
res.push(elem);
}
}
res
}
pub fn errors(&self) -> Vec<&Diagnostic> {
let mut vec = Vec::new();
for diag in &self.contents {
if matches!(diag.level, Level::Error) {
vec.push(diag);
}
}
vec
}
pub fn warning_contains(&self, message: &str) -> bool {
let warnings = self.warnings();
for warning in warnings {
if warning.message == message {
return true;
}
}
false
}
pub fn contains_message(&self, message: &str) -> bool {
for item in &self.contents {
if item.message == message {
return true;
}
}
false
}
pub fn sort_and_dedup(&mut self) {
self.contents.sort();
self.contents.dedup();
}
}
fn convert_diagnostic(
msg: &Diagnostic,
file_id: &HashMap<usize, usize>,
) -> diagnostic::Diagnostic<usize> {
let diagnostic = diagnostic::Diagnostic::new(match msg.level {
Level::Debug => diagnostic::Severity::Help,
Level::Info => diagnostic::Severity::Note,
Level::Error => diagnostic::Severity::Error,
Level::Warning => diagnostic::Severity::Warning,
})
.with_message(msg.message.to_owned());
let mut labels = Vec::new();
if let Loc::File(file_no, start, end) = msg.loc {
labels.push(diagnostic::Label::primary(file_id[&file_no], start..end));
}
for note in &msg.notes {
if let Loc::File(file_no, start, end) = note.loc {
labels.push(
diagnostic::Label::secondary(file_id[&file_no], start..end)
.with_message(note.message.to_owned()),
);
} else {
unreachable!("note without file position");
}
}
if labels.is_empty() {
diagnostic
} else {
diagnostic.with_labels(labels)
}
}
impl Namespace {
pub fn print_diagnostics_in_plain(&self, cache: &FileResolver, debug: bool) {
let (files, file_id) = self.convert_files(cache);
let config = term::Config::default();
for msg in self.diagnostics.iter() {
if msg.level == Level::Debug && !debug {
continue;
}
let diagnostic = convert_diagnostic(msg, &file_id);
let mut buffer = RawBuffer::new();
term::emit(&mut buffer, &config, &files, &diagnostic).unwrap();
println!("{}", buffer.into_string());
}
}
pub fn print_diagnostics(&self, cache: &FileResolver, debug: bool) {
let (files, file_id) = self.convert_files(cache);
let writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always);
let config = term::Config::default();
for msg in self.diagnostics.iter() {
if msg.level == Level::Debug && !debug {
continue;
}
let diagnostic = convert_diagnostic(msg, &file_id);
term::emit(&mut writer.lock(), &config, &files, &diagnostic).unwrap();
}
}
pub fn diagnostics_as_json(&self, cache: &FileResolver) -> Vec<OutputJson> {
let (files, file_id) = self.convert_files(cache);
let mut json = Vec::new();
let config = term::Config {
display_style: term::DisplayStyle::Short,
..Default::default()
};
for msg in self.diagnostics.iter() {
if msg.level == Level::Info || msg.level == Level::Debug {
continue;
}
let diagnostic = convert_diagnostic(msg, &file_id);
let mut buffer = RawBuffer::new();
term::emit(&mut buffer, &config, &files, &diagnostic).unwrap();
let location = if let Loc::File(file_no, start, end) = msg.loc {
Some(LocJson {
file: format!("{}", self.files[file_no]),
start: start + 1,
end: end + 1,
})
} else {
None
};
json.push(OutputJson {
sourceLocation: location,
ty: format!("{:?}", msg.ty),
component: "general".to_owned(),
severity: msg.level.to_string().to_owned(),
message: msg.message.to_owned(),
formattedMessage: buffer.into_string(),
});
}
json
}
fn convert_files(
&self,
cache: &FileResolver,
) -> (files::SimpleFiles<String, Arc<str>>, HashMap<usize, usize>) {
let mut files = files::SimpleFiles::new();
let mut file_id = HashMap::new();
for (file_no, file) in self.files.iter().enumerate() {
if file.cache_no.is_some() {
let (contents, _) = cache.get_file_contents_and_number(&file.path);
file_id.insert(file_no, files.add(format!("{}", file), contents.to_owned()));
}
}
(files, file_id)
}
}
pub struct RawBuffer {
buf: Vec<u8>,
}
impl RawBuffer {
#[allow(clippy::new_without_default)]
pub fn new() -> RawBuffer {
RawBuffer { buf: Vec::new() }
}
pub fn into_string(self) -> String {
String::from_utf8(self.buf).unwrap()
}
}
impl io::Write for RawBuffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl term::termcolor::WriteColor for RawBuffer {
fn supports_color(&self) -> bool {
false
}
fn set_color(&mut self, _: &term::termcolor::ColorSpec) -> io::Result<()> {
Ok(())
}
fn reset(&mut self) -> io::Result<()> {
Ok(())
}
}