#[derive(Debug, Clone, PartialEq)]
pub struct Diagnostic {
pub headline: String,
pub path: String,
pub line: u32,
pub col: u32,
pub source_line: String,
pub message: String,
pub hint: Option<String>,
}
pub const DEFAULT_HEADLINE: &str = "very error. much confuse.";
pub(crate) fn split_source_lines(source: &str) -> Vec<String> {
source
.split('\n')
.map(|line| line.strip_suffix('\r').unwrap_or(line).to_string())
.collect()
}
pub(crate) fn source_line(lines: &[String], line: u32) -> String {
lines
.get((line as usize).saturating_sub(1))
.cloned()
.unwrap_or_default()
}
impl Diagnostic {
pub fn new(
path: impl Into<String>,
line: u32,
col: u32,
source_line: impl Into<String>,
message: impl Into<String>,
) -> Diagnostic {
Diagnostic {
headline: DEFAULT_HEADLINE.to_string(),
path: path.into(),
line,
col,
source_line: source_line.into(),
message: message.into(),
hint: None,
}
}
pub fn with_headline(mut self, headline: impl Into<String>) -> Diagnostic {
self.headline = headline.into();
self
}
pub fn with_hint(mut self, hint: impl Into<String>) -> Diagnostic {
self.hint = Some(hint.into());
self
}
pub fn render(&self) -> String {
let mut out = String::new();
out.push_str(&self.headline);
out.push_str("\n\n");
out.push_str(&format!(" {}:{}\n", self.path, self.line));
out.push_str(&format!(" {}\n", self.source_line));
let caret_pad = 4 + self.col.saturating_sub(1) as usize;
out.push_str(&" ".repeat(caret_pad));
out.push_str(&format!("^ {}\n", self.message));
if let Some(hint) = &self.hint {
out.push_str(&format!("\nsuch fix: {hint}\n"));
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_matches_design_section_7() {
let diag = Diagnostic::new(
"examples/hello.doge",
4,
14,
"bark \"hello\" + 5",
"cannot + a Str and an Int",
)
.with_hint("turn the Int into a Str first, e.g. str(5)");
let expected = "\
very error. much confuse.
examples/hello.doge:4
bark \"hello\" + 5
^ cannot + a Str and an Int
such fix: turn the Int into a Str first, e.g. str(5)
";
assert_eq!(diag.render(), expected);
}
#[test]
fn render_without_hint_omits_fix_block() {
let diag = Diagnostic::new("f.doge", 1, 1, "wut", "unexpected");
let rendered = diag.render();
assert!(!rendered.contains("such fix:"));
assert!(rendered.ends_with("^ unexpected\n"));
}
}