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}