use std::path::PathBuf;
use rustdoc_types::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Warning,
Error,
}
#[derive(Debug, Clone)]
pub struct SourceLocation {
pub file_path: PathBuf,
pub display_path: String,
pub begin: (usize, usize),
pub end: (usize, usize),
pub label: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub severity: Severity,
pub message: String,
pub source_location: Option<SourceLocation>,
pub help: Option<String>,
pub notes: Vec<String>,
pub error_chain: Vec<String>,
pub has_hidden_causes: bool,
}
pub struct DiagnosticSink {
diagnostics: Vec<Diagnostic>,
workspace_root: PathBuf,
debug: bool,
}
impl DiagnosticSink {
pub fn new(workspace_root: PathBuf, debug: bool) -> Self {
Self {
diagnostics: Vec::new(),
workspace_root,
debug,
}
}
pub fn has_hidden_causes(&self) -> bool {
self.diagnostics.iter().any(|d| d.has_hidden_causes)
}
pub fn warning(&mut self, msg: impl Into<String>) -> DiagnosticBuilder<'_> {
DiagnosticBuilder {
sink: self,
diagnostic: Diagnostic {
severity: Severity::Warning,
message: msg.into(),
source_location: None,
help: None,
notes: Vec::new(),
error_chain: Vec::new(),
has_hidden_causes: false,
},
}
}
pub fn error(&mut self, msg: impl Into<String>) -> DiagnosticBuilder<'_> {
DiagnosticBuilder {
sink: self,
diagnostic: Diagnostic {
severity: Severity::Error,
message: msg.into(),
source_location: None,
help: None,
notes: Vec::new(),
error_chain: Vec::new(),
has_hidden_causes: false,
},
}
}
pub fn is_empty(&self) -> bool {
self.diagnostics.is_empty()
}
pub fn drain(&mut self) -> Vec<Diagnostic> {
std::mem::take(&mut self.diagnostics)
}
}
pub struct DiagnosticBuilder<'a> {
sink: &'a mut DiagnosticSink,
diagnostic: Diagnostic,
}
impl DiagnosticBuilder<'_> {
pub fn with_span(mut self, span: &Span) -> Self {
let ws_root = &self.sink.workspace_root;
let file_path = if span.filename.is_absolute() {
span.filename.clone()
} else {
ws_root.join(&span.filename)
};
let display_path = pathdiff::diff_paths(&file_path, ws_root)
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| file_path.display().to_string());
self.diagnostic.source_location = Some(SourceLocation {
file_path,
display_path,
begin: span.begin,
end: span.end,
label: None,
});
self
}
pub fn with_span_if(self, span: Option<&Span>) -> Self {
if let Some(span) = span {
self.with_span(span)
} else {
self
}
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
if let Some(ref mut loc) = self.diagnostic.source_location {
loc.label = Some(label.into());
}
self
}
pub fn with_help(mut self, help: impl Into<String>) -> Self {
self.diagnostic.help = Some(help.into());
self
}
#[allow(dead_code)]
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.diagnostic.notes.push(note.into());
self
}
pub fn with_error_chain(mut self, error: &dyn std::error::Error) -> Self {
self.diagnostic.error_chain.push(error.to_string());
let mut current = error.source();
if self.sink.debug {
while let Some(cause) = current {
self.diagnostic.error_chain.push(cause.to_string());
current = cause.source();
}
} else if current.is_some() {
self.diagnostic.has_hidden_causes = true;
}
self
}
pub fn emit(self) {
self.sink.diagnostics.push(self.diagnostic);
}
}