doge_compiler/
diagnostics.rs1#[derive(Debug, Clone, PartialEq)]
4pub struct Diagnostic {
5 pub headline: String,
8 pub path: String,
10 pub line: u32,
12 pub col: u32,
14 pub source_line: String,
16 pub message: String,
18 pub hint: Option<String>,
20}
21
22pub const DEFAULT_HEADLINE: &str = "very error. much confuse.";
24
25pub(crate) fn split_source_lines(source: &str) -> Vec<String> {
29 source
30 .split('\n')
31 .map(|line| line.strip_suffix('\r').unwrap_or(line).to_string())
32 .collect()
33}
34
35pub(crate) fn source_line(lines: &[String], line: u32) -> String {
39 lines
40 .get((line as usize).saturating_sub(1))
41 .cloned()
42 .unwrap_or_default()
43}
44
45impl Diagnostic {
46 pub fn new(
48 path: impl Into<String>,
49 line: u32,
50 col: u32,
51 source_line: impl Into<String>,
52 message: impl Into<String>,
53 ) -> Diagnostic {
54 Diagnostic {
55 headline: DEFAULT_HEADLINE.to_string(),
56 path: path.into(),
57 line,
58 col,
59 source_line: source_line.into(),
60 message: message.into(),
61 hint: None,
62 }
63 }
64
65 pub fn with_headline(mut self, headline: impl Into<String>) -> Diagnostic {
67 self.headline = headline.into();
68 self
69 }
70
71 pub fn with_hint(mut self, hint: impl Into<String>) -> Diagnostic {
73 self.hint = Some(hint.into());
74 self
75 }
76
77 pub fn render(&self) -> String {
92 let mut out = String::new();
93 out.push_str(&self.headline);
94 out.push_str("\n\n");
95
96 out.push_str(&format!(" {}:{}\n", self.path, self.line));
97
98 out.push_str(&format!(" {}\n", self.source_line));
99
100 let caret_pad = 4 + self.col.saturating_sub(1) as usize;
101 out.push_str(&" ".repeat(caret_pad));
102 out.push_str(&format!("^ {}\n", self.message));
103
104 if let Some(hint) = &self.hint {
105 out.push_str(&format!("\nsuch fix: {hint}\n"));
106 }
107
108 out
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn render_matches_design_section_7() {
118 let diag = Diagnostic::new(
121 "examples/hello.doge",
122 4,
123 14,
124 "bark \"hello\" + 5",
125 "cannot + a Str and an Int",
126 )
127 .with_hint("turn the Int into a Str first, e.g. str(5)");
128
129 let expected = "\
130very error. much confuse.
131
132 examples/hello.doge:4
133 bark \"hello\" + 5
134 ^ cannot + a Str and an Int
135
136such fix: turn the Int into a Str first, e.g. str(5)
137";
138 assert_eq!(diag.render(), expected);
139 }
140
141 #[test]
142 fn render_without_hint_omits_fix_block() {
143 let diag = Diagnostic::new("f.doge", 1, 1, "wut", "unexpected");
144 let rendered = diag.render();
145 assert!(!rendered.contains("such fix:"));
146 assert!(rendered.ends_with("^ unexpected\n"));
147 }
148}