Skip to main content

pine_diagnostics/
lib.rs

1//! A single diagnostic type shared across the Pine toolchain.
2//!
3//! Both `pine-sema` (which reports errors) and `pine-lint` (which reports
4//! warnings) emit this same [`Diagnostic`], so a consumer can collect, sort,
5//! and render findings from every phase through one code path. `Severity`
6//! — not the crate that produced it — decides how a finding is treated.
7
8use std::fmt;
9
10/// How serious a finding is.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Severity {
13    /// The program is invalid; it should not be executed.
14    Error,
15    /// Legal but suspect or non-idiomatic; does not block execution.
16    Warning,
17}
18
19impl fmt::Display for Severity {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Severity::Error => f.write_str("error"),
23            Severity::Warning => f.write_str("warning"),
24        }
25    }
26}
27
28/// A single finding from any analysis phase.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Diagnostic {
31    pub rule: &'static str,
32    pub severity: Severity,
33    pub message: String,
34    pub pos: Option<(u32, u32)>,
35}
36
37impl Diagnostic {
38    pub fn new(
39        rule: &'static str,
40        severity: Severity,
41        pos: Option<(u32, u32)>,
42        message: impl Into<String>,
43    ) -> Self {
44        Self {
45            rule,
46            severity,
47            message: message.into(),
48            pos,
49        }
50    }
51
52    pub fn error(rule: &'static str, pos: Option<(u32, u32)>, message: impl Into<String>) -> Self {
53        Self::new(rule, Severity::Error, pos, message)
54    }
55
56    pub fn warning(
57        rule: &'static str,
58        pos: Option<(u32, u32)>,
59        message: impl Into<String>,
60    ) -> Self {
61        Self::new(rule, Severity::Warning, pos, message)
62    }
63
64    /// 1-based line, if located.
65    pub fn line(&self) -> Option<u32> {
66        self.pos.map(|(line, _)| line)
67    }
68
69    /// 1-based column, if located.
70    pub fn column(&self) -> Option<u32> {
71        self.pos.map(|(_, col)| col)
72    }
73}
74
75impl fmt::Display for Diagnostic {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        match self.pos {
78            Some((line, col)) => write!(
79                f,
80                "{sev} [{rule}] {line}:{col}: {msg}",
81                sev = self.severity,
82                rule = self.rule,
83                msg = self.message,
84            ),
85            None => write!(
86                f,
87                "{sev} [{rule}]: {msg}",
88                sev = self.severity,
89                rule = self.rule,
90                msg = self.message,
91            ),
92        }
93    }
94}