Skip to main content

adoctra_core/
diagnostic.rs

1//! Parse diagnostics.
2//!
3//! For an editor, syntax problems must be *data*, not a hard failure: the
4//! parser always returns a (possibly partial) document plus a list of
5//! diagnostics. Real failures (I/O in backends) still use [`crate::Error`].
6
7use crate::ast::{Document, Span};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Severity {
11    Error,
12    Warning,
13    Info,
14}
15
16impl Severity {
17    pub fn as_str(self) -> &'static str {
18        match self {
19            Severity::Error => "error",
20            Severity::Warning => "warning",
21            Severity::Info => "info",
22        }
23    }
24}
25
26#[derive(Debug, Clone, PartialEq)]
27pub struct Diagnostic {
28    /// For an omitted source line, `span` is an empty insertion-point anchor
29    /// and this range identifies the exact bytes in `SourceFile::omitted_lines`.
30    pub omitted_span: Option<crate::OmittedSourceSpan>,
31    pub span: Span,
32    pub severity: Severity,
33    pub message: String,
34    /// Conversion-facing presentation. Physical editor data above is retained.
35    pub report: DiagnosticReport,
36}
37
38/// A logger cursor need not identify the physical span (selected includes and
39/// reader lookahead can change its file and line in the reference processor).
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct DiagnosticCursor {
42    pub source_id: crate::SourceId,
43    pub line: usize,
44}
45
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
47pub enum DiagnosticLocation {
48    /// Use the physical span, or resolve its reader cursor before returning.
49    #[default]
50    Source,
51    Unlocated,
52    Cursor(DiagnosticCursor),
53    /// Ruby's `reader.cursor_at_prev_line`: the cursor of the span's line
54    /// minus one (line 0 for the first line, as Ruby prints it).
55    PreviousLine,
56}
57
58/// Presentation overrides for conversion logs; never used to rewrite spans or
59/// remove editor diagnostics. Unspecified severity/text use the diagnostic's.
60#[derive(Debug, Clone, Default, PartialEq)]
61pub struct DiagnosticReport {
62    pub suppressed: bool,
63    /// Ruby logs it only when its logger is at INFO level (`if logger.info?`):
64    /// a CLI shows it with `-v` (and its quiet NullLogger records it).
65    pub verbose_only: bool,
66    pub severity: Option<Severity>,
67    pub message: Option<String>,
68    pub location: DiagnosticLocation,
69    /// Reported while converting the body (Ruby's `Document#convert`), not
70    /// while loading it: a host that only loads (Ruby's `-o /dev/null`)
71    /// does not log it.
72    pub conversion: bool,
73}
74
75impl DiagnosticReport {
76    pub(crate) fn unterminated(name: &str) -> Self {
77        Self {
78            severity: Some(Severity::Warning),
79            message: Some(format!("unterminated {name} block")),
80            ..Self::default()
81        }
82    }
83}
84
85/// The result of parsing for editor use: always succeeds, carries diagnostics.
86#[derive(Debug, Clone, PartialEq)]
87pub struct ParseResult {
88    /// Source highlighting from the same structural parse as `document`.
89    /// Tokens crossing includes are split into physical source fragments.
90    pub tokens: Vec<crate::Token>,
91    pub document: Document,
92    pub diagnostics: Vec<Diagnostic>,
93    /// Source trivia omitted from the semantic tree, in source order.
94    pub comments: Vec<Span>,
95    /// Recognized conditional syntax (single-line content is excluded).
96    pub conditional_directives: Vec<Span>,
97    /// Lines omitted by inactive conditional branches.
98    pub inactive: Vec<Span>,
99    /// Include directives processed through an explicit provider.
100    pub include_directives: Vec<Span>,
101    /// Ordered original fragments for spans carrying a provenance index.
102    pub provenance: Vec<Vec<Span>>,
103}