Skip to main content

nounsql_core/
diag.rs

1use crate::span::{LineIndex, Span};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum Severity {
5    Error,
6    Warning,
7}
8
9#[derive(Debug, Clone)]
10pub struct Label {
11    pub span: Span,
12    pub message: String,
13}
14
15#[derive(Debug, Clone)]
16pub struct Diagnostic {
17    pub severity: Severity,
18    pub message: String,
19    pub span: Span,
20    /// 関連位置。mixin や blueprint の定義元を指すのに使う。
21    pub labels: Vec<Label>,
22}
23
24impl Diagnostic {
25    pub fn error(span: Span, message: impl Into<String>) -> Self {
26        Self {
27            severity: Severity::Error,
28            message: message.into(),
29            span,
30            labels: Vec::new(),
31        }
32    }
33
34    pub fn warning(span: Span, message: impl Into<String>) -> Self {
35        Self {
36            severity: Severity::Warning,
37            message: message.into(),
38            span,
39            labels: Vec::new(),
40        }
41    }
42
43    pub fn with_label(mut self, span: Span, message: impl Into<String>) -> Self {
44        self.labels.push(Label {
45            span,
46            message: message.into(),
47        });
48        self
49    }
50}
51
52/// 診断をテキストに描画する。
53pub fn render(src: &str, path: &str, diags: &[Diagnostic]) -> String {
54    let index = LineIndex::new(src);
55    let mut out = String::new();
56    for d in diags {
57        let pos = index.line_col(d.span.start);
58        let tag = match d.severity {
59            Severity::Error => "error",
60            Severity::Warning => "warning",
61        };
62        out.push_str(&format!(
63            "{tag}: {msg}\n  --> {path}:{line}:{col}\n",
64            msg = d.message,
65            line = pos.line,
66            col = pos.col
67        ));
68        out.push_str(&render_snippet(src, &index, d.span));
69        for label in &d.labels {
70            let lpos = index.line_col(label.span.start);
71            out.push_str(&format!(
72                "  note: {} ({path}:{}:{})\n",
73                label.message, lpos.line, lpos.col
74            ));
75            out.push_str(&render_snippet(src, &index, label.span));
76        }
77        out.push('\n');
78    }
79    out
80}
81
82fn render_snippet(src: &str, index: &LineIndex, span: Span) -> String {
83    let pos = index.line_col(span.start);
84    let text = index.line_text(src, pos.line);
85    let width = span.end.saturating_sub(span.start).max(1);
86    let indent = " ".repeat(pos.col - 1);
87    let carets = "^".repeat(width);
88    format!("{:>4} | {text}\n     | {indent}{carets}\n", pos.line)
89}