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    pub file: Option<String>,
36}
37
38impl Diagnostic {
39    pub fn new(
40        rule: &'static str,
41        severity: Severity,
42        pos: Option<(u32, u32)>,
43        message: impl Into<String>,
44    ) -> Self {
45        Self {
46            rule,
47            severity,
48            message: message.into(),
49            pos,
50            file: None,
51        }
52    }
53
54    /// Attribute this finding to `file` (an imported library). `None` leaves it
55    /// on the main script.
56    pub fn in_file(mut self, file: Option<String>) -> Self {
57        self.file = file;
58        self
59    }
60
61    pub fn error(rule: &'static str, pos: Option<(u32, u32)>, message: impl Into<String>) -> Self {
62        Self::new(rule, Severity::Error, pos, message)
63    }
64
65    pub fn warning(
66        rule: &'static str,
67        pos: Option<(u32, u32)>,
68        message: impl Into<String>,
69    ) -> Self {
70        Self::new(rule, Severity::Warning, pos, message)
71    }
72
73    /// 1-based line, if located.
74    pub fn line(&self) -> Option<u32> {
75        self.pos.map(|(line, _)| line)
76    }
77
78    /// 1-based column, if located.
79    pub fn column(&self) -> Option<u32> {
80        self.pos.map(|(_, col)| col)
81    }
82}
83
84impl fmt::Display for Diagnostic {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        let sev = self.severity;
87        let rule = self.rule;
88        let msg = &self.message;
89        match (&self.file, self.pos) {
90            (None, Some((line, col))) => write!(f, "{sev} [{rule}] {line}:{col}: {msg}"),
91            (None, None) => write!(f, "{sev} [{rule}]: {msg}"),
92            (Some(path), Some((line, col))) => {
93                write!(f, "{sev} [{rule}] {path}:{line}:{col}: {msg}")
94            }
95            (Some(path), None) => write!(f, "{sev} [{rule}] {path}: {msg}"),
96        }
97    }
98}