Skip to main content

hidane_rules/
diag.rs

1//! Diagnostics: the `issues[]` of a successful `PUT :securityRules` and the lines of its
2//! "Error compiling rules:" message.
3
4use crate::ast::Span;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Severity {
8    Warning,
9    Error,
10}
11
12/// Where an issue is, as the official emulator reports it: line and column are 1-based, the
13/// offsets count code points from the start of the file, and `end_offset` is the last code
14/// point of the span (inclusive).
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Position {
17    pub line: u32,
18    pub column: u32,
19    pub current_offset: u32,
20    pub end_offset: u32,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Issue {
25    /// The file's name as the request gave it; `None` when it had none.
26    pub file_name: Option<String>,
27    /// `None` for issues about the whole ruleset (the old version warning), printed `L0:0`.
28    pub position: Option<Position>,
29    pub description: String,
30    pub severity: Severity,
31}
32
33impl Issue {
34    /// `L<line>:<column> <description>`, a line of the error message.
35    pub fn line(&self) -> String {
36        let (line, column) = self.position.map_or((0, 0), |p| (p.line, p.column));
37        format!("L{line}:{column} {}", self.description)
38    }
39}
40
41/// An issue found in one file, before it is given the file's name and code point offsets.
42#[derive(Debug, Clone)]
43pub(crate) struct Diag {
44    pub span: Option<Span>,
45    pub message: String,
46    pub severity: Severity,
47}
48
49impl Diag {
50    pub fn error(span: Span, message: impl Into<String>) -> Self {
51        Self {
52            span: Some(span),
53            message: message.into(),
54            severity: Severity::Error,
55        }
56    }
57
58    pub fn warning(span: Span, message: impl Into<String>) -> Self {
59        Self {
60            span: Some(span),
61            message: message.into(),
62            severity: Severity::Warning,
63        }
64    }
65
66    pub fn into_issue(self, offsets: &Offsets, file_name: Option<&str>) -> Issue {
67        Issue {
68            file_name: file_name.map(str::to_owned),
69            position: self.span.map(|span| Position {
70                line: span.line,
71                column: span.column,
72                current_offset: offsets.at(span.start),
73                // The last code point of the span; an empty span ends where it starts.
74                end_offset: offsets.at(span.end).max(offsets.at(span.start) + 1) - 1,
75            }),
76            description: self.message,
77            severity: self.severity,
78        }
79    }
80}
81
82/// How many code points precede each byte offset of a file (the byte offset itself in ASCII).
83pub(crate) struct Offsets {
84    len: usize,
85    counts: Option<Vec<u32>>,
86}
87
88impl Offsets {
89    pub fn new(src: &str) -> Self {
90        let counts = (!src.is_ascii()).then(|| {
91            let mut counts = Vec::with_capacity(src.len() + 1);
92            for (count, c) in src.chars().enumerate() {
93                let count = u32::try_from(count).unwrap_or(u32::MAX);
94                counts.extend(std::iter::repeat_n(count, c.len_utf8()));
95            }
96            counts.push(u32::try_from(src.chars().count()).unwrap_or(u32::MAX));
97            counts
98        });
99        Self {
100            len: src.len(),
101            counts,
102        }
103    }
104
105    fn at(&self, offset: usize) -> u32 {
106        let offset = offset.min(self.len);
107        match &self.counts {
108            Some(counts) => counts[offset],
109            None => u32::try_from(offset).unwrap_or(u32::MAX),
110        }
111    }
112}
113
114/// The official order: by line, column and then text (`L0:0` first), duplicates kept.
115pub(crate) fn sort(diags: &mut [Diag]) {
116    diags.sort_by(|a, b| {
117        let key = |d: &Diag| d.span.map_or((0, 0), |s| (s.line, s.column));
118        key(a).cmp(&key(b)).then_with(|| a.message.cmp(&b.message))
119    });
120}