cratestack_parser/
diagnostics_related.rs1use std::ops::Range;
2use std::sync::Arc;
3
4use ariadne::{Color, Label, Report, ReportKind};
5use cratestack_core::schema::attribute_text::substitute_for_display;
6
7use crate::diagnostics::{SchemaError, char_span};
8
9#[derive(Clone)]
17pub struct RelatedSpan {
18 pub(crate) message: String,
19 pub(crate) span: Range<usize>,
20 pub(crate) line: usize,
21 pub(crate) file: Arc<str>,
22 pub(crate) source_text: Arc<str>,
23}
24
25impl RelatedSpan {
26 pub(crate) fn new(message: impl Into<String>, span: Range<usize>, line: usize) -> Self {
27 Self {
28 message: message.into(),
29 span,
30 line,
31 file: Arc::from(""),
32 source_text: Arc::from(""),
33 }
34 }
35
36 pub fn message(&self) -> &str {
37 &self.message
38 }
39
40 pub fn span(&self) -> Range<usize> {
42 self.span.clone()
43 }
44
45 pub fn line(&self) -> usize {
46 self.line
47 }
48
49 pub fn file(&self) -> &str {
50 &self.file
51 }
52}
53
54impl std::fmt::Debug for RelatedSpan {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("RelatedSpan")
58 .field("message", &self.message)
59 .field("span", &self.span)
60 .field("line", &self.line)
61 .field("file", &self.file)
62 .finish()
63 }
64}
65
66impl SchemaError {
67 pub(crate) fn render_with_related(&self) -> String {
71 let mut files: Vec<(String, String)> = Vec::new();
72 let mut register = |file: &str, text: &str| {
73 if !files.iter().any(|(name, _)| name == file) {
74 files.push((file.to_owned(), substitute_for_display(text)));
75 }
76 };
77 register(&self.file, &self.source_text);
78 for related in &self.related {
79 register(&related.file, &related.source_text);
80 }
81 let primary = (
82 self.file.to_string(),
83 char_span(&self.source_text, &self.span),
84 );
85 let mut report = Report::build(ReportKind::Error, primary.clone())
86 .with_message(&self.message)
87 .with_label(
88 Label::new(primary)
89 .with_message(&self.message)
90 .with_color(Color::Red),
91 );
92 for related in &self.related {
93 let span = char_span(&related.source_text, &related.span);
94 report = report.with_label(
95 Label::new((related.file.to_string(), span))
96 .with_message(&related.message)
97 .with_color(Color::Blue),
98 );
99 }
100 let mut output = Vec::new();
101 report
102 .finish()
103 .write(ariadne::sources(files), &mut output)
104 .expect("diagnostic rendering should succeed");
105 String::from_utf8(output).expect("ariadne should emit utf-8")
106 }
107}