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