Skip to main content

surf_parse/
error.rs

1use serde::{Deserialize, Serialize};
2
3use crate::types::Span;
4
5/// Errors that can occur during parsing.
6#[derive(Debug, Clone, thiserror::Error)]
7pub enum ParseError {
8    #[error("Invalid front matter: {message}")]
9    FrontMatter { message: String, span: Span },
10
11    #[error("Unclosed block directive '{name}' opened at line {line}")]
12    UnclosedBlock { name: String, line: usize },
13
14    #[error("Invalid attribute syntax: {message}")]
15    InvalidAttrs { message: String, span: Span },
16}
17
18/// A diagnostic message produced during parsing.
19///
20/// Diagnostics are non-fatal: the parser continues and produces a best-effort
21/// result even when diagnostics are emitted.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Diagnostic {
24    pub severity: Severity,
25    pub message: String,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub span: Option<Span>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub code: Option<String>,
30    /// Deterministic auto-fix for this diagnostic, when one exists.
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub fix: Option<Fix>,
33}
34
35/// A single span-based text edit against the original source.
36///
37/// `span` is BYTE-offset based (`start_offset`/`end_offset`) against the
38/// CRLF-normalised source the diagnostics were produced from. Offsets always
39/// sit on UTF-8 character boundaries. An empty span (`start_offset ==
40/// end_offset`) is an insertion.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TextEdit {
43    /// Byte range in the original source to replace.
44    pub span: Span,
45    /// Text spliced in place of the spanned bytes.
46    pub replacement: String,
47}
48
49/// Safety tier of a [`Fix`]. Ordered: `Safe < Suggested`, so a requested tier
50/// of `Suggested` also applies every `Safe` fix.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
52#[serde(rename_all = "lowercase")]
53pub enum FixSafety {
54    /// Mechanical and behaviour-preserving; always safe to auto-apply.
55    Safe,
56    /// Probably right but involves a judgement call (e.g. a rename based on a
57    /// did-you-mean match); applied only when explicitly requested.
58    Suggested,
59}
60
61/// A deterministic auto-fix: one or more text edits applied to the ORIGINAL
62/// source bytes (never an AST round-trip), so untouched bytes are preserved
63/// exactly.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct Fix {
66    /// Edits to apply, all anchored to the same original source.
67    pub edits: Vec<TextEdit>,
68    /// Safety tier controlling when the fix is auto-applied.
69    pub safety: FixSafety,
70    /// Human-readable description, e.g. `"replace '::section' with '## X'"`.
71    pub description: String,
72}
73
74/// Severity level for diagnostics.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "lowercase")]
77pub enum Severity {
78    Error,
79    Warning,
80    Info,
81}