1use crate::renderer::source_map::SourceMap;
4use crate::Level;
5use std::borrow::Cow;
6use std::ops::Range;
7
8pub(crate) const ERROR_TXT: &str = "error";
9pub(crate) const HELP_TXT: &str = "help";
10pub(crate) const INFO_TXT: &str = "info";
11pub(crate) const NOTE_TXT: &str = "note";
12pub(crate) const WARNING_TXT: &str = "warning";
13
14pub type Report<'a> = &'a [Group<'a>];
23
24#[derive(Clone, Debug, Default)]
25pub(crate) struct Id<'a> {
26 pub(crate) id: Option<Cow<'a, str>>,
27 pub(crate) url: Option<Cow<'a, str>>,
28}
29
30#[doc = include_str!("../examples/highlight_message.rs")]
43#[doc = include_str!("../examples/highlight_message.svg")]
45#[derive(Clone, Debug)]
46pub struct Group<'a> {
47 pub(crate) primary_level: Level<'a>,
48 pub(crate) title: Option<Title<'a>>,
49 pub(crate) elements: Vec<Element<'a>>,
50}
51
52impl<'a> Group<'a> {
53 pub fn with_title(title: Title<'a>) -> Self {
55 let level = title.level.clone();
56 let mut x = Self::with_level(level);
57 x.title = Some(title);
58 x
59 }
60
61 #[doc = include_str!("../examples/elide_header.rs")]
68 #[doc = include_str!("../examples/elide_header.svg")]
70 pub fn with_level(level: Level<'a>) -> Self {
71 Self {
72 primary_level: level,
73 title: None,
74 elements: vec![],
75 }
76 }
77
78 pub fn element(mut self, section: impl Into<Element<'a>>) -> Self {
80 self.elements.push(section.into());
81 self
82 }
83
84 pub fn elements(mut self, sections: impl IntoIterator<Item = impl Into<Element<'a>>>) -> Self {
86 self.elements.extend(sections.into_iter().map(Into::into));
87 self
88 }
89
90 pub fn is_empty(&self) -> bool {
91 self.elements.is_empty() && self.title.is_none()
92 }
93}
94
95#[derive(Clone, Debug)]
97#[non_exhaustive]
98pub enum Element<'a> {
99 Message(Message<'a>),
100 Cause(Snippet<'a, Annotation<'a>>),
101 Suggestion(Snippet<'a, Patch<'a>>),
102 Origin(Origin<'a>),
103 Padding(Padding),
104}
105
106impl<'a> From<Message<'a>> for Element<'a> {
107 fn from(value: Message<'a>) -> Self {
108 Element::Message(value)
109 }
110}
111
112impl<'a> From<Snippet<'a, Annotation<'a>>> for Element<'a> {
113 fn from(value: Snippet<'a, Annotation<'a>>) -> Self {
114 Element::Cause(value)
115 }
116}
117
118impl<'a> From<Snippet<'a, Patch<'a>>> for Element<'a> {
119 fn from(value: Snippet<'a, Patch<'a>>) -> Self {
120 Element::Suggestion(value)
121 }
122}
123
124impl<'a> From<Origin<'a>> for Element<'a> {
125 fn from(value: Origin<'a>) -> Self {
126 Element::Origin(value)
127 }
128}
129
130impl From<Padding> for Element<'_> {
131 fn from(value: Padding) -> Self {
132 Self::Padding(value)
133 }
134}
135
136#[derive(Clone, Debug)]
138pub struct Padding;
139
140#[derive(Clone, Debug)]
158pub struct Title<'a> {
159 pub(crate) level: Level<'a>,
160 pub(crate) id: Option<Id<'a>>,
161 pub(crate) text: Cow<'a, str>,
162 pub(crate) allows_styling: bool,
163}
164
165impl<'a> Title<'a> {
166 pub fn id(mut self, id: impl Into<Cow<'a, str>>) -> Self {
178 self.id.get_or_insert(Id::default()).id = Some(id.into());
179 self
180 }
181
182 pub fn id_url(mut self, url: impl Into<Cow<'a, str>>) -> Self {
190 self.id.get_or_insert(Id::default()).url = Some(url.into());
191 self
192 }
193
194 pub fn element(self, section: impl Into<Element<'a>>) -> Group<'a> {
196 Group::with_title(self).element(section)
197 }
198
199 pub fn elements(self, sections: impl IntoIterator<Item = impl Into<Element<'a>>>) -> Group<'a> {
201 Group::with_title(self).elements(sections)
202 }
203}
204
205#[derive(Clone, Debug)]
209pub struct Message<'a> {
210 pub(crate) level: Level<'a>,
211 pub(crate) text: Cow<'a, str>,
212}
213
214#[derive(Clone, Debug)]
222pub struct Snippet<'a, T> {
223 pub(crate) path: Option<Cow<'a, str>>,
224 pub(crate) line_start: usize,
225 pub(crate) source: Cow<'a, str>,
226 pub(crate) markers: Vec<T>,
227 pub(crate) fold: bool,
228}
229
230impl<'a, T: Clone> Snippet<'a, T> {
231 pub fn source(source: impl Into<Cow<'a, str>>) -> Self {
241 Self {
242 path: None,
243 line_start: 1,
244 source: source.into(),
245 markers: vec![],
246 fold: true,
247 }
248 }
249
250 pub fn line_start(mut self, line_start: usize) -> Self {
253 self.line_start = line_start;
254 self
255 }
256
257 pub fn path(mut self, path: impl Into<OptionCow<'a>>) -> Self {
267 self.path = path.into().0;
268 self
269 }
270
271 pub fn fold(mut self, fold: bool) -> Self {
277 self.fold = fold;
278 self
279 }
280}
281
282impl<'a> Snippet<'a, Annotation<'a>> {
283 pub fn annotation(mut self, annotation: Annotation<'a>) -> Snippet<'a, Annotation<'a>> {
285 self.markers.push(annotation);
286 self
287 }
288
289 pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
291 self.markers.extend(annotation);
292 self
293 }
294}
295
296impl<'a> Snippet<'a, Patch<'a>> {
297 pub fn patch(mut self, patch: Patch<'a>) -> Snippet<'a, Patch<'a>> {
299 self.markers.push(patch);
300 self
301 }
302
303 pub fn patches(mut self, patches: impl IntoIterator<Item = Patch<'a>>) -> Self {
305 self.markers.extend(patches);
306 self
307 }
308}
309
310#[doc = include_str!("../examples/expected_type.rs")]
319#[doc = include_str!("../examples/expected_type.svg")]
322#[derive(Clone, Debug)]
323pub struct Annotation<'a> {
324 pub(crate) span: Range<usize>,
325 pub(crate) label: Option<Cow<'a, str>>,
326 pub(crate) kind: AnnotationKind,
327 pub(crate) highlight_source: bool,
328}
329
330impl<'a> Annotation<'a> {
331 pub fn label(mut self, label: impl Into<OptionCow<'a>>) -> Self {
343 self.label = label.into().0;
344 self
345 }
346
347 pub fn highlight_source(mut self, highlight_source: bool) -> Self {
351 self.highlight_source = highlight_source;
352 self
353 }
354}
355
356#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
358#[non_exhaustive]
359pub enum AnnotationKind {
360 Primary,
364 Context,
371 #[doc = include_str!("../examples/struct_name_as_context.rs")]
384 #[doc = include_str!("../examples/struct_name_as_context.svg")]
387 Visible,
389}
390
391impl AnnotationKind {
392 pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> {
394 Annotation {
395 span,
396 label: None,
397 kind: self,
398 highlight_source: false,
399 }
400 }
401
402 pub(crate) fn is_primary(&self) -> bool {
403 matches!(self, AnnotationKind::Primary)
404 }
405}
406
407#[doc = include_str!("../examples/multi_suggestion.rs")]
416#[doc = include_str!("../examples/multi_suggestion.svg")]
419#[derive(Clone, Debug)]
420pub struct Patch<'a> {
421 pub(crate) span: Range<usize>,
422 pub(crate) replacement: Cow<'a, str>,
423}
424
425impl<'a> Patch<'a> {
426 pub fn new(span: Range<usize>, replacement: impl Into<Cow<'a, str>>) -> Self {
436 Self {
437 span,
438 replacement: replacement.into(),
439 }
440 }
441
442 pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool {
443 !self.replacement.is_empty() && !self.replaces_meaningful_content(sm)
444 }
445
446 pub(crate) fn is_deletion(&self, sm: &SourceMap<'_>) -> bool {
447 self.replacement.trim().is_empty() && self.replaces_meaningful_content(sm)
448 }
449
450 pub(crate) fn is_replacement(&self, sm: &SourceMap<'_>) -> bool {
451 !self.replacement.is_empty() && self.replaces_meaningful_content(sm)
452 }
453
454 pub(crate) fn is_destructive_replacement(&self, sm: &SourceMap<'_>) -> bool {
459 self.is_replacement(sm)
460 && !sm
461 .span_to_snippet(self.span.clone())
462 .map_or(false, |s| {
464 as_substr(s.trim(), self.replacement.trim()).is_some()
465 })
466 }
467
468 fn replaces_meaningful_content(&self, sm: &SourceMap<'_>) -> bool {
469 sm.span_to_snippet(self.span.clone())
470 .map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty())
471 }
472
473 pub(crate) fn trim_trivial_replacements(&mut self, sm: &'a SourceMap<'a>) {
476 if self.replacement.is_empty() {
477 return;
478 }
479 let Some(snippet) = sm.span_to_snippet(self.span.clone()) else {
480 return;
481 };
482
483 if let Some((prefix, substr, suffix)) = as_substr(snippet, &self.replacement) {
484 self.span = self.span.start + prefix..self.span.end.saturating_sub(suffix);
485 self.replacement = Cow::Owned(substr.to_owned());
486 }
487 }
488}
489
490#[derive(Clone, Debug)]
506pub struct Origin<'a> {
507 pub(crate) path: Cow<'a, str>,
508 pub(crate) line: Option<usize>,
509 pub(crate) char_column: Option<usize>,
510}
511
512impl<'a> Origin<'a> {
513 pub fn path(path: impl Into<Cow<'a, str>>) -> Self {
521 Self {
522 path: path.into(),
523 line: None,
524 char_column: None,
525 }
526 }
527
528 pub fn line(mut self, line: usize) -> Self {
530 self.line = Some(line);
531 self
532 }
533
534 pub fn char_column(mut self, char_column: usize) -> Self {
542 self.char_column = Some(char_column);
543 self
544 }
545}
546
547impl<'a> From<Cow<'a, str>> for Origin<'a> {
548 fn from(origin: Cow<'a, str>) -> Self {
549 Self::path(origin)
550 }
551}
552
553#[derive(Debug)]
554pub struct OptionCow<'a>(pub(crate) Option<Cow<'a, str>>);
555
556impl<'a, T: Into<Cow<'a, str>>> From<Option<T>> for OptionCow<'a> {
557 fn from(value: Option<T>) -> Self {
558 Self(value.map(Into::into))
559 }
560}
561
562impl<'a> From<&'a Cow<'a, str>> for OptionCow<'a> {
563 fn from(value: &'a Cow<'a, str>) -> Self {
564 Self(Some(Cow::Borrowed(value)))
565 }
566}
567
568impl<'a> From<Cow<'a, str>> for OptionCow<'a> {
569 fn from(value: Cow<'a, str>) -> Self {
570 Self(Some(value))
571 }
572}
573
574impl<'a> From<&'a str> for OptionCow<'a> {
575 fn from(value: &'a str) -> Self {
576 Self(Some(Cow::Borrowed(value)))
577 }
578}
579impl<'a> From<String> for OptionCow<'a> {
580 fn from(value: String) -> Self {
581 Self(Some(Cow::Owned(value)))
582 }
583}
584
585impl<'a> From<&'a String> for OptionCow<'a> {
586 fn from(value: &'a String) -> Self {
587 Self(Some(Cow::Borrowed(value.as_str())))
588 }
589}
590
591fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a str, usize)> {
596 let common_prefix = original
597 .chars()
598 .zip(suggestion.chars())
599 .take_while(|(c1, c2)| c1 == c2)
600 .map(|(c, _)| c.len_utf8())
601 .sum();
602 let original = &original[common_prefix..];
603 let suggestion = &suggestion[common_prefix..];
604 if let Some(stripped) = suggestion.strip_suffix(original) {
605 let common_suffix = original.len();
606 Some((common_prefix, stripped, common_suffix))
607 } else {
608 None
609 }
610}