1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use ariadne::{Color, Label, Report, ReportKind};
use std::{io, ops::Range};

pub type Span = Range<usize>;

pub struct Diagnostic {
	filename: String,
	code: String,
	reports: Vec<Report<(String, Span)>>,
	fail: bool
}

impl Diagnostic {
	pub fn new(filename: String, code: String) -> Self {
		Self {
			filename,
			code,
			reports: Vec::new(),
			fail: false
		}
	}

	pub fn is_fail(&self) -> bool {
		self.fail
	}

	pub fn print(&self) -> io::Result<()> {
		self.print_to(io::stderr())
	}

	pub fn print_to<W: io::Write>(&self, mut w: W) -> io::Result<()> {
		let mut cache = (self.filename.clone(), self.code.clone().into());
		for r in &self.reports {
			r.write(&mut cache, &mut w)?;
		}
		Ok(())
	}

	fn offset(&self, at: proc_macro2::LineColumn) -> usize {
		let line_offset: usize = self
			.code
			.split('\n')
			.take(at.line - 1)
			.map(|line| line.len() + 1)
			.sum();
		line_offset + at.column
	}

	/// Info without a code label.
	pub fn info<T>(&mut self, msg: T)
	where
		T: ToString
	{
		self.reports.push(
			Report::build(
				ReportKind::Custom("info", Color::Green),
				self.filename.clone(),
				0
			)
			.with_message(msg)
			.finish()
		);
	}

	/// Warning without a code label.
	pub fn warn<T>(&mut self, msg: T)
	where
		T: ToString
	{
		self.reports.push(
			Report::build(ReportKind::Warning, self.filename.clone(), 0)
				.with_message(msg)
				.finish()
		);
	}

	/// Warning with a code label.
	pub fn warn_with_label<T, L>(&mut self, msg: T, span: proc_macro2::Span, label: L)
	where
		T: ToString,
		L: ToString
	{
		let span = self.offset(span.start()) .. self.offset(span.end());
		self.reports.push(
			Report::build(ReportKind::Warning, self.filename.clone(), span.start)
				.with_message(msg)
				.with_label(Label::new((self.filename.clone(), span)).with_message(label))
				.finish()
		);
	}

	/// Syntax error with the code span from syn's error.
	pub fn syntax_error(&mut self, err: syn::Error) {
		let mut report = Report::build(
			ReportKind::Error,
			self.filename.clone(),
			self.offset(err.span().start())
		);
		report.set_message("Syntax Error");
		for err in err {
			let span = err.span();
			report.add_label(
				Label::new((
					self.filename.clone(),
					self.offset(span.start()) .. self.offset(span.end())
				))
				.with_message(err)
			);
		}
		self.reports.push(report.finish());
		self.fail = true;
	}

	/// Error without a code label.
	pub fn error<T>(&mut self, msg: T)
	where
		T: ToString
	{
		self.reports.push(
			Report::build(ReportKind::Error, self.filename.clone(), 0)
				.with_message(msg)
				.finish()
		);
		self.fail = true;
	}
}