Skip to main content

eure_mark/
report.rs

1//! Error reporting for eure-mark using eure's ErrorReports system
2
3use eure::document::OriginMap;
4use eure::query::assets::TextFile;
5use eure::query_flow::{Db, QueryError};
6use eure::report::{ErrorReport, ErrorReports, Origin};
7use eure::tree::Cst;
8use eure_tree::tree::InputSpan;
9
10use crate::check::CheckResult;
11use crate::error::ReferenceError;
12
13/// Context for converting eure-mark errors to ErrorReports
14pub struct EumdReportContext<'a> {
15    /// File for the eumd document
16    pub file: TextFile,
17    /// CST for span resolution
18    pub cst: &'a Cst,
19    /// Origin map with precise origins
20    pub origins: &'a OriginMap,
21}
22
23/// Convert eure-mark check result to ErrorReports
24pub fn report_check_errors(result: &CheckResult, ctx: &EumdReportContext<'_>) -> ErrorReports {
25    result
26        .errors
27        .iter()
28        .map(|error| report_reference_error(error, ctx))
29        .collect()
30}
31
32fn report_reference_error(error: &ReferenceError, ctx: &EumdReportContext<'_>) -> ErrorReport {
33    let title = format!(
34        "Undefined !{}[{}] {}",
35        error.ref_type, error.key, error.location
36    );
37
38    // Try to get span from NodeId if available
39    // FIXME: Multiple fallback paths to EMPTY span without is_fallback flag:
40    // 1. When node_id, offset, or len is None
41    // 2. When get_value_span returns None
42    // Both cases silently report errors at file start instead of indicating uncertainty.
43    let span = if let (Some(node_id), Some(offset), Some(len)) =
44        (error.node_id, error.offset, error.len)
45    {
46        // Get the span of the node (code block)
47        if let Some(node_span) = ctx.origins.get_value_span(node_id, ctx.cst) {
48            // For code blocks, we need to find where the actual content starts
49            // The node_span includes the opening ``` and language tag
50            // We'll calculate the content start by looking at the text
51            let content_start = get_code_block_content_start(ctx, node_span);
52
53            let start = content_start + offset;
54            let end = start + len;
55            InputSpan { start, end }
56        } else {
57            InputSpan::EMPTY
58        }
59    } else {
60        InputSpan::EMPTY
61    };
62
63    let origin = Origin::new(ctx.file.clone(), span);
64    ErrorReport::error(title, origin)
65}
66
67/// Get the byte offset where the code block content starts
68fn get_code_block_content_start(_ctx: &EumdReportContext<'_>, node_span: InputSpan) -> u32 {
69    // For code blocks, we need to skip past the opening ``` and language tag
70    // The Text type in eure stores the content without the delimiters,
71    // but the node span includes everything.
72    //
73    // For now, use the node span start which points to the content area.
74    // The span resolution in OriginMap should give us a reasonable position.
75    node_span.start
76}
77
78/// Format check errors to a string using annotate-snippets.
79///
80/// Returns `Err` with suspension if file content isn't loaded yet.
81pub fn format_check_errors(
82    db: &impl Db,
83    result: &CheckResult,
84    file: TextFile,
85    cst: &Cst,
86    origins: &OriginMap,
87    styled: bool,
88) -> Result<String, QueryError> {
89    let ctx = EumdReportContext { file, cst, origins };
90    let reports = report_check_errors(result, &ctx);
91    eure::report::format_error_reports(db, &reports, styled)
92}
93
94/// Format check errors without ANSI colors (for testing).
95///
96/// Returns `Err` with suspension if file content isn't loaded yet.
97pub fn format_check_errors_plain(
98    db: &impl Db,
99    result: &CheckResult,
100    file: TextFile,
101    cst: &Cst,
102    origins: &OriginMap,
103) -> Result<String, QueryError> {
104    format_check_errors(db, result, file, cst, origins, false)
105}