yash_syntax/source/pretty.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Pretty-printing diagnostic messages containing references to source code
18//!
19//! This module defines some data types for constructing intermediate data
20//! structures for printing diagnostic messages referencing source code
21//! fragments. When you have an [`Error`](crate::parser::Error), you can
22//! convert it to a [`Report`]. Then, you can in turn convert it into
23//! `annotate_snippets::Snippet`, for example, and finally format a printable
24//! diagnostic message string.
25//!
26//! When the `yash_syntax` crate is built with the `annotate-snippets` feature
27//! enabled, it supports conversion from [`Report`] to
28//! [`Group`](annotate_snippets::Group). If you would like to use another
29//! formatter instead, you can provide your own conversion for yourself.
30//!
31//! ## Printing an error
32//!
33//! This example shows how to format an [`Error`](crate::parser::Error) instance
34//! into a human-readable string.
35//!
36//! ```
37//! # use yash_syntax::parser::{Error, ErrorCause, SyntaxError};
38//! # use yash_syntax::source::Location;
39//! # use yash_syntax::source::pretty::Report;
40//! let error = Error {
41//! cause: ErrorCause::Syntax(SyntaxError::EmptyParam),
42//! location: Location::dummy(""),
43//! };
44//! let report = Report::from(&error);
45//! // The lines below require the `annotate-snippets` feature.
46//! # #[cfg(feature = "annotate-snippets")]
47//! # {
48//! let group = annotate_snippets::Group::from(&report);
49//! eprintln!("{}", annotate_snippets::Renderer::plain().render(&[group]));
50//! # }
51//! ```
52//!
53//! You can also implement conversion from your custom error object to a
54//! [`Report`], which then can be used in the same way to format a diagnostic
55//! message. To do this, implement `From<YourError>` or `From<&YourError>` for
56//! `Report`.
57//!
58//! ## Deprecated items
59//!
60//! Before [`Report`] was introduced, this module provided [`Message`] as the
61//! main data structure for constructing a diagnostic message. `Message` could
62//! be constructed directly or through the [`MessageBase`] trait, which
63//! provided a blanket implementation of `From<&T> for Message` for
64//! implementors of the trait. These items are now deprecated in favor of
65//! `Report`, but are available for backward compatibility for now. Please
66//! migrate to `Report` before they are removed in a future release.
67
68use super::Location;
69use std::borrow::Cow;
70use std::cell::Ref;
71use std::ops::{Deref, Range};
72use std::rc::Rc;
73
74/// Type of [`Report`]
75#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
76#[non_exhaustive]
77pub enum ReportType {
78 #[default]
79 None,
80 Error,
81 Warning,
82}
83
84/// Type and label annotating a [`Span`]
85#[derive(Clone, Debug, Eq, PartialEq)]
86#[non_exhaustive]
87pub enum SpanRole<'a> {
88 /// Primary span, usually indicating the main cause of a problem
89 Primary { label: Cow<'a, str> },
90 /// Secondary span, usually indicating related information
91 Supplementary { label: Cow<'a, str> },
92 // Patch { replacement: Cow<'a, str> },
93}
94
95/// Part of source code [`Snippet`] annotated with additional information
96#[derive(Clone, Debug, Eq, PartialEq)]
97pub struct Span<'a> {
98 /// Range of bytes in the source code
99 pub range: Range<usize>,
100 /// Type and label of this span
101 pub role: SpanRole<'a>,
102}
103
104/// Fragment of source code with annotated spans highlighting specific regions
105///
106/// A snippet corresponds to a single source [`Code`](super::Code). It contains
107/// zero or more [`Span`]s that annotate specific parts of the code.
108///
109/// `Snippet` holds a [`Ref`] to the string held in `self.code.value`, which
110/// provides an access to the string without making a new borrow
111/// ([`code_string`](Self::code_string)). This allows creating another
112/// message builder such as `annotate_snippets::Snippet` without the need to
113/// retain a borrow of `self.code.value`.
114#[derive(Debug)]
115pub struct Snippet<'a> {
116 /// Source code to which the spans refer
117 pub code: &'a super::Code,
118 /// Reference to the string held in `self.code.value`
119 code_string: Ref<'a, str>,
120 /// Spans describing parts of the code
121 pub spans: Vec<Span<'a>>,
122}
123
124impl Snippet<'_> {
125 /// Creates a new snippet for the given code without any spans.
126 #[must_use]
127 pub fn with_code(code: &super::Code) -> Snippet<'_> {
128 Self::with_code_and_spans(code, Vec::new())
129 }
130
131 /// Creates a new snippet for the given code with the given spans.
132 #[must_use]
133 pub fn with_code_and_spans<'a>(code: &'a super::Code, spans: Vec<Span<'a>>) -> Snippet<'a> {
134 Snippet {
135 code,
136 code_string: Ref::map(code.value.borrow(), String::as_str),
137 spans,
138 }
139 }
140
141 /// Creates a vector containing a snippet with a primary span.
142 ///
143 /// This is a convenience function for creating a vector of snippets
144 /// containing a primary span created from the given location and label.
145 /// The returned vector can be used as the `snippets` field of a
146 /// [`Report`].
147 ///
148 /// This function calls
149 /// [`Source::extend_with_context`](super::Source::extend_with_context) for
150 /// `location.code.source`, thereby adding supplementary spans describing the
151 /// context of the source code. This means that the returned vector may
152 /// contain multiple snippets or spans if the source has a related location.
153 #[must_use]
154 pub fn with_primary_span<'a>(location: &'a Location, label: Cow<'a, str>) -> Vec<Snippet<'a>> {
155 let range = location.byte_range();
156 let role = SpanRole::Primary { label };
157 let spans = vec![Span { range, role }];
158 let mut snippets = vec![Snippet::with_code_and_spans(&location.code, spans)];
159 location.code.source.extend_with_context(&mut snippets);
160 snippets
161 }
162
163 /// Returns the string held in `self.code.value`.
164 ///
165 /// This method returns a reference to the string held in `self.code.value`.
166 /// `Snippet` internally holds a `Ref` to the string, which provides an
167 /// access to the string without making a new borrow.
168 #[inline(always)]
169 #[must_use]
170 pub fn code_string(&self) -> &str {
171 &self.code_string
172 }
173}
174
175impl Clone for Snippet<'_> {
176 fn clone(&self) -> Self {
177 Snippet {
178 code: self.code,
179 code_string: Ref::clone(&self.code_string),
180 spans: self.spans.clone(),
181 }
182 }
183}
184
185impl PartialEq<Snippet<'_>> for Snippet<'_> {
186 fn eq(&self, other: &Snippet<'_>) -> bool {
187 self.code == other.code && self.spans == other.spans
188 }
189}
190
191impl Eq for Snippet<'_> {}
192
193/// Type of [`Footnote`]
194#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
195#[non_exhaustive]
196pub enum FootnoteType {
197 /// No specific type
198 #[default]
199 None,
200 /// For footnotes that provide additional information
201 Note,
202 /// For footnotes that provide suggestions
203 Suggestion,
204}
205
206/// Message without associated source code
207#[derive(Clone, Debug, Default, Eq, PartialEq)]
208pub struct Footnote<'a> {
209 /// Type of this footnote
210 pub r#type: FootnoteType,
211 /// Text of this footnote
212 pub label: Cow<'a, str>,
213}
214
215/// Entire report containing multiple snippets
216///
217/// `Report` is an intermediate data structure for constructing a diagnostic
218/// message. It contains multiple [`Snippet`]s, each of which corresponds to a
219/// specific part of the source code being analyzed.
220/// See the [module documentation](self) for more details.
221#[derive(Clone, Debug, Default, Eq, PartialEq)]
222#[non_exhaustive]
223pub struct Report<'a> {
224 /// Type of this report
225 pub r#type: ReportType,
226 /// Optional identifier of this report (e.g., error code)
227 pub id: Option<Cow<'a, str>>,
228 /// Main caption of this report
229 pub title: Cow<'a, str>,
230 /// Source code fragments annotated with additional information
231 pub snippets: Vec<Snippet<'a>>,
232 /// Additional message without associated source code
233 pub footnotes: Vec<Footnote<'a>>,
234}
235
236impl Report<'_> {
237 /// Creates a new, empty report.
238 #[inline]
239 #[must_use]
240 pub fn new() -> Self {
241 Report::default()
242 }
243}
244
245/// Type of annotation.
246#[deprecated(note = "Use `ReportType` or `FootnoteType` instead", since = "0.16.0")]
247#[derive(Clone, Copy, Debug, Eq, PartialEq)]
248pub enum AnnotationType {
249 Error,
250 Warning,
251 Info,
252 Note,
253 Help,
254}
255
256/// Source code fragment annotated with a label
257///
258/// Annotations are part of an entire [`Message`].
259#[deprecated(note = "Use `Snippet` and `Span` instead", since = "0.16.0")]
260#[derive(Clone)]
261pub struct Annotation<'a> {
262 /// Type of annotation
263 #[allow(deprecated)]
264 pub r#type: AnnotationType,
265 /// String that describes the annotated part of the source code
266 pub label: Cow<'a, str>,
267 /// Position of the annotated fragment in the source code
268 pub location: &'a Location,
269 /// Annotated code string
270 ///
271 /// This value provides an access to the string held in
272 /// `self.location.code.value`, which can only be accessed by a `Ref`.
273 pub code: Rc<dyn Deref<Target = str> + 'a>,
274}
275
276#[allow(deprecated)]
277impl std::fmt::Debug for Annotation<'_> {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 f.debug_struct("Annotation")
280 .field("type", &self.r#type)
281 .field("label", &self.label)
282 .field("location", &self.location)
283 .field("code", &&**self.code)
284 .finish()
285 }
286}
287
288#[allow(deprecated)]
289impl<'a> Annotation<'a> {
290 /// Creates a new annotation.
291 ///
292 /// This function makes a borrow of `location.code.value` and stores it in
293 /// `self.code`. If it has been mutually borrowed, this function panics.
294 pub fn new(r#type: AnnotationType, label: Cow<'a, str>, location: &'a Location) -> Self {
295 Annotation {
296 r#type,
297 label,
298 location,
299 code: Rc::new(Ref::map(location.code.value.borrow(), String::as_str)),
300 }
301 }
302}
303
304/// Additional message without associated source code
305#[deprecated(note = "Use `Footnote` instead", since = "0.16.0")]
306#[derive(Clone, Debug)]
307pub struct Footer<'a> {
308 /// Type of this footer
309 #[allow(deprecated)]
310 pub r#type: AnnotationType,
311 /// Text of this footer
312 pub label: Cow<'a, str>,
313}
314
315/// Entire diagnostic message
316#[allow(deprecated)]
317#[deprecated(note = "Use `Report` instead", since = "0.16.0")]
318#[derive(Clone, Debug)]
319pub struct Message<'a> {
320 /// Type of this message
321 pub r#type: AnnotationType,
322 /// String that communicates the most important information in this message
323 pub title: Cow<'a, str>,
324 /// References to source code fragments annotated with additional information
325 pub annotations: Vec<Annotation<'a>>,
326 /// Additional text without associated source code
327 pub footers: Vec<Footer<'a>>,
328}
329
330/// Returns a mutable reference to the snippet for the given code, creating it
331/// if necessary.
332///
333/// This is a utility function used in constructing a vector of snippets.
334///
335/// If a snippet for the given code already exists in the vector, this function
336/// returns a mutable reference to that snippet. Otherwise, it creates a new
337/// snippet with the given code and appends it to the vector, returning a
338/// mutable reference to the newly created snippet.
339pub fn snippet_for_code<'a, 'b>(
340 snippets: &'b mut Vec<Snippet<'a>>,
341 code: &'a super::Code,
342) -> &'b mut Snippet<'a> {
343 // if let Some(snippet) = snippets.iter_mut().find(|s| std::ptr::eq(s.code, code)) {
344 // snippet
345 if let Some(i) = snippets.iter().position(|s| std::ptr::eq(s.code, code)) {
346 &mut snippets[i]
347 } else {
348 // TODO Use Vec::push_mut when stabilized
349 snippets.push(Snippet::with_code(code));
350 snippets.last_mut().unwrap()
351 }
352}
353
354/// Adds a span to the appropriate snippet in the given vector.
355///
356/// This is a utility function used in constructing a vector of snippets with
357/// annotated spans.
358///
359/// If a snippet for the given code already exists in the vector, this function
360/// adds the span to that snippet. Otherwise, it creates a new snippet with the
361/// given code and span, and appends it to the vector.
362pub fn add_span<'a>(code: &'a super::Code, span: Span<'a>, snippets: &mut Vec<Snippet<'a>>) {
363 snippet_for_code(snippets, code).spans.push(span);
364}
365
366#[test]
367fn test_add_span_with_matching_code() {
368 let code = Rc::new(super::Code {
369 value: std::cell::RefCell::new("echo hello".to_string()),
370 start_line_number: std::num::NonZero::new(1).unwrap(),
371 source: Rc::new(super::Source::CommandString),
372 });
373 let span = Span {
374 range: 5..10,
375 role: SpanRole::Primary {
376 label: "greeting".into(),
377 },
378 };
379 let mut snippets = vec![Snippet::with_code(&code)];
380
381 add_span(&code, span, &mut snippets);
382
383 assert_eq!(snippets.len(), 1);
384 assert_eq!(snippets[0].spans.len(), 1);
385 assert_eq!(snippets[0].spans[0].range, 5..10);
386 assert_eq!(
387 snippets[0].spans[0].role,
388 SpanRole::Primary {
389 label: "greeting".into()
390 }
391 );
392}
393
394#[test]
395fn test_add_span_without_matching_code() {
396 let code1 = Rc::new(super::Code {
397 value: std::cell::RefCell::new("echo hello".to_string()),
398 start_line_number: std::num::NonZero::new(1).unwrap(),
399 source: Rc::new(super::Source::CommandString),
400 });
401 let code2 = Rc::new(super::Code {
402 value: std::cell::RefCell::new("ls -l".to_string()),
403 start_line_number: std::num::NonZero::new(1).unwrap(),
404 source: Rc::new(super::Source::CommandString),
405 });
406 let span = Span {
407 range: 0..2,
408 role: SpanRole::Primary {
409 label: "list".into(),
410 },
411 };
412 let mut snippets = vec![Snippet::with_code(&code1)];
413
414 add_span(&code2, span, &mut snippets);
415
416 assert_eq!(snippets.len(), 2);
417 assert_eq!(snippets[0].code.value.borrow().as_str(), "echo hello");
418 assert_eq!(snippets[0].spans.len(), 0);
419 assert_eq!(snippets[1].code.value.borrow().as_str(), "ls -l");
420 assert_eq!(snippets[1].spans.len(), 1);
421 assert_eq!(snippets[1].spans[0].range, 0..2);
422 assert_eq!(
423 snippets[1].spans[0].role,
424 SpanRole::Primary {
425 label: "list".into()
426 }
427 );
428}
429
430impl super::Source {
431 /// Extends the given vector of snippets with spans annotating the context of this source.
432 ///
433 /// If `self` is a source that has a related location (e.g., the `original` field of
434 /// `CommandSubst`), this method adds one or more spans describing the location to the given
435 /// vector. If the `code` of the location is already present in the vector, it adds the span
436 /// to the existing snippet; otherwise, it creates a new snippet.
437 ///
438 /// If `self` does not have a related location, this method does nothing.
439 pub fn extend_with_context<'a>(&'a self, snippets: &mut Vec<Snippet<'a>>) {
440 use super::Source::*;
441 match self {
442 Unknown
443 | Stdin
444 | CommandString
445 | CommandFile { .. }
446 | VariableValue { .. }
447 | InitFile { .. }
448 | Other { .. } => (),
449
450 CommandSubst { original } => {
451 let range = original.byte_range();
452 let role = SpanRole::Supplementary {
453 label: "command substitution appeared here".into(),
454 };
455 add_span(&original.code, Span { range, role }, snippets);
456 }
457
458 Arith { original } => {
459 let range = original.byte_range();
460 let role = SpanRole::Supplementary {
461 label: "arithmetic expansion appeared here".into(),
462 };
463 add_span(&original.code, Span { range, role }, snippets);
464 }
465
466 Eval { original } => {
467 let range = original.byte_range();
468 let role = SpanRole::Supplementary {
469 label: "command passed to the eval built-in here".into(),
470 };
471 add_span(&original.code, Span { range, role }, snippets);
472 }
473
474 DotScript { name, origin } => {
475 let range = origin.byte_range();
476 let role = SpanRole::Supplementary {
477 label: format!("script `{name}` was sourced here").into(),
478 };
479 add_span(&origin.code, Span { range, role }, snippets);
480 }
481
482 Trap { origin, .. } => {
483 let range = origin.byte_range();
484 let role = SpanRole::Supplementary {
485 label: "trap was set here".into(),
486 };
487 add_span(&origin.code, Span { range, role }, snippets);
488 }
489
490 Alias { original, alias } => {
491 // Where the alias was substituted
492 let range = original.byte_range();
493 let role = SpanRole::Supplementary {
494 label: format!("alias `{}` was substituted here", alias.name).into(),
495 };
496 add_span(&original.code, Span { range, role }, snippets);
497 // Recurse into the source of the substituted code
498 original.code.source.extend_with_context(snippets);
499
500 // Where the alias was defined
501 let range = alias.origin.byte_range();
502 let role = SpanRole::Supplementary {
503 label: format!("alias `{}` was defined here", alias.name).into(),
504 };
505 add_span(&alias.origin.code, Span { range, role }, snippets);
506 // Recurse into the source of the alias definition
507 alias.origin.code.source.extend_with_context(snippets);
508 }
509 }
510 }
511
512 /// Appends complementary annotations describing this source.
513 #[allow(deprecated)]
514 #[deprecated(note = "Use `extend_with_context` instead", since = "0.16.0")]
515 pub fn complement_annotations<'a, 's: 'a, T: Extend<Annotation<'a>>>(&'s self, result: &mut T) {
516 use super::Source::*;
517 match self {
518 Unknown
519 | Stdin
520 | CommandString
521 | CommandFile { .. }
522 | VariableValue { .. }
523 | InitFile { .. }
524 | Other { .. } => (),
525
526 CommandSubst { original } => {
527 // TODO Use Extend::extend_one
528 result.extend(std::iter::once(Annotation::new(
529 AnnotationType::Info,
530 "command substitution appeared here".into(),
531 original,
532 )));
533 }
534 Arith { original } => {
535 // TODO Use Extend::extend_one
536 result.extend(std::iter::once(Annotation::new(
537 AnnotationType::Info,
538 "arithmetic expansion appeared here".into(),
539 original,
540 )));
541 }
542 Eval { original } => {
543 // TODO Use Extend::extend_one
544 result.extend(std::iter::once(Annotation::new(
545 AnnotationType::Info,
546 "command passed to the eval built-in here".into(),
547 original,
548 )));
549 }
550 DotScript { name, origin } => {
551 // TODO Use Extend::extend_one
552 result.extend(std::iter::once(Annotation::new(
553 AnnotationType::Info,
554 format!("script `{name}` was sourced here",).into(),
555 origin,
556 )));
557 }
558 Trap { origin, .. } => {
559 // TODO Use Extend::extend_one
560 result.extend(std::iter::once(Annotation::new(
561 AnnotationType::Info,
562 "trap was set here".into(),
563 origin,
564 )));
565 }
566 Alias { original, alias } => {
567 // TODO Use Extend::extend_one
568 result.extend(std::iter::once(Annotation::new(
569 AnnotationType::Info,
570 format!("alias `{}` was substituted here", alias.name).into(),
571 original,
572 )));
573 original.code.source.complement_annotations(result);
574 result.extend(std::iter::once(Annotation::new(
575 AnnotationType::Info,
576 format!("alias `{}` was defined here", alias.name).into(),
577 &alias.origin,
578 )));
579 alias.origin.code.source.complement_annotations(result);
580 }
581 }
582 }
583}
584
585/// Helper for constructing a [`Message`]
586///
587/// Thanks to the blanket implementation `impl<'a, T: MessageBase> From<&'a T>
588/// for Message<'a>`, implementors of this trait can be converted to a message
589/// for free.
590#[allow(deprecated)]
591#[deprecated(note = "Use `Report` instead", since = "0.16.0")]
592pub trait MessageBase {
593 /// Returns the type of the entire message.
594 ///
595 /// The default implementation returns `AnnotationType::Error`.
596 fn message_type(&self) -> AnnotationType {
597 AnnotationType::Error
598 }
599
600 // TODO message tag
601
602 /// Returns the main caption of the message.
603 fn message_title(&self) -> Cow<'_, str>;
604
605 /// Returns an annotation to be the first in the message.
606 fn main_annotation(&self) -> Annotation<'_>;
607
608 /// Adds additional annotations to the given container.
609 ///
610 /// The default implementation does nothing.
611 fn additional_annotations<'a, T: Extend<Annotation<'a>>>(&'a self, results: &mut T) {
612 let _ = results;
613 }
614
615 /// Returns footers that are included in the message.
616 fn footers(&self) -> Vec<Footer<'_>> {
617 Vec::new()
618 }
619}
620
621/// Constructs a message based on the message base.
622#[allow(deprecated)]
623impl<'a, T: MessageBase> From<&'a T> for Message<'a> {
624 fn from(base: &'a T) -> Self {
625 let main_annotation = base.main_annotation();
626 let main_source = &main_annotation.location.code.source;
627 let mut annotations = vec![main_annotation];
628
629 main_source.complement_annotations(&mut annotations);
630 base.additional_annotations(&mut annotations);
631
632 Message {
633 r#type: base.message_type(),
634 title: base.message_title(),
635 annotations,
636 footers: base.footers(),
637 }
638 }
639}
640
641#[cfg(feature = "annotate-snippets")]
642mod annotate_snippets_support {
643 use super::*;
644
645 impl From<ReportType> for annotate_snippets::Level<'_> {
646 fn from(r#type: ReportType) -> Self {
647 use ReportType::*;
648 match r#type {
649 None => Self::INFO.no_name(),
650 Error => Self::ERROR,
651 Warning => Self::WARNING,
652 }
653 }
654 }
655
656 /// Converts `yash_syntax::source::pretty::Span` into
657 /// `annotate_snippets::Annotation`.
658 ///
659 /// This conversion is not provided as a public `From<&Span> for Annotation` implementation
660 /// because a future variant of `SpanRole` may map to
661 /// `annotate_snippets::Patch` instead of `annotate_snippets::Annotation`.
662 fn span_to_annotation<'a>(span: &'a Span<'a>) -> annotate_snippets::Annotation<'a> {
663 use annotate_snippets::AnnotationKind as AK;
664 let (kind, label) = match &span.role {
665 SpanRole::Primary { label } => (AK::Primary, label),
666 SpanRole::Supplementary { label } => (AK::Context, label),
667 };
668 kind.span(span.range.clone()).label(label)
669 }
670
671 // `From<&Snippet>` is not implemented for
672 // `annotate_snippets::Snippet<'_, annotate_snippets::Annotation<'_>>`
673 // because a future variant of `SpanRole` may map to
674 // `annotate_snippets::Patch` instead of `annotate_snippets::Annotation`.
675
676 /// Converts `yash_syntax::source::pretty::Snippet` into
677 /// `annotate_snippets::Snippet<'a, annotate_snippets::Annotation<'a>>`.
678 ///
679 /// This conversion is not provided as a public `From<&Snippet> for Snippet` implementation
680 /// because a future variant of `SpanRole` may map to
681 /// `annotate_snippets::Patch` instead of `annotate_snippets::Annotation`, which does not fit
682 /// into a single `annotate_snippets::Snippet<'a, annotate_snippets::Annotation<'a>>`.
683 fn snippet_to_annotation_snippet<'a>(
684 snippet: &'a Snippet<'a>,
685 ) -> annotate_snippets::Snippet<'a, annotate_snippets::Annotation<'a>> {
686 annotate_snippets::Snippet::source(snippet.code_string())
687 .line_start(
688 snippet
689 .code
690 .start_line_number
691 .get()
692 .try_into()
693 .unwrap_or(usize::MAX),
694 )
695 .path(snippet.code.source.label())
696 .annotations(snippet.spans.iter().map(span_to_annotation))
697 }
698
699 /// Converts `yash_syntax::source::pretty::FootnoteType` into
700 /// `annotate_snippets::Level`.
701 ///
702 /// This implementation is only available when the `yash_syntax` crate is
703 /// built with the `annotate-snippets` feature enabled.
704 impl From<FootnoteType> for annotate_snippets::Level<'_> {
705 fn from(r#type: FootnoteType) -> Self {
706 use FootnoteType::*;
707 match r#type {
708 None => Self::INFO.no_name(),
709 Note => Self::NOTE,
710 Suggestion => Self::HELP,
711 }
712 }
713 }
714
715 /// Converts `yash_syntax::source::pretty::Footnote` into
716 /// `annotate_snippets::Message`.
717 ///
718 /// This implementation is only available when the `yash_syntax` crate is
719 /// built with the `annotate-snippets` feature enabled.
720 impl<'a> From<Footnote<'a>> for annotate_snippets::Message<'a> {
721 fn from(footer: Footnote<'a>) -> Self {
722 annotate_snippets::Level::from(footer.r#type).message(footer.label)
723 }
724 }
725
726 /// Converts `&yash_syntax::source::pretty::Footnote` into
727 /// `annotate_snippets::Message`.
728 ///
729 /// This implementation is only available when the `yash_syntax` crate is
730 /// built with the `annotate-snippets` feature enabled.
731 impl<'a> From<&'a Footnote<'a>> for annotate_snippets::Message<'a> {
732 fn from(footer: &'a Footnote<'a>) -> Self {
733 annotate_snippets::Level::from(footer.r#type).message(&*footer.label)
734 }
735 }
736
737 /// Converts `yash_syntax::source::pretty::Report` into
738 /// `annotate_snippets::Group`.
739 ///
740 /// This implementation is only available when the `yash_syntax` crate is
741 /// built with the `annotate-snippets` feature enabled.
742 impl<'a> From<&'a Report<'a>> for annotate_snippets::Group<'a> {
743 fn from(report: &'a Report<'a>) -> Self {
744 let title = annotate_snippets::Level::from(report.r#type).primary_title(&*report.title);
745 let title = if let Some(id) = &report.id {
746 title.id(&**id)
747 } else {
748 title
749 };
750
751 title
752 .elements(report.snippets.iter().map(snippet_to_annotation_snippet))
753 .elements(
754 report
755 .footnotes
756 .iter()
757 .map(annotate_snippets::Message::from),
758 )
759 }
760 }
761
762 /// Converts `yash_syntax::source::pretty::AnnotationType` into
763 /// `annotate_snippets::Level`.
764 ///
765 /// This implementation is only available when the `yash_syntax` crate is
766 /// built with the `annotate-snippets` feature enabled.
767 #[allow(deprecated)]
768 impl<'a> From<AnnotationType> for annotate_snippets::Level<'a> {
769 fn from(r#type: AnnotationType) -> Self {
770 use AnnotationType::*;
771 match r#type {
772 Error => Self::ERROR,
773 Warning => Self::WARNING,
774 Info => Self::INFO,
775 Note => Self::NOTE,
776 Help => Self::HELP,
777 }
778 }
779 }
780
781 /// Converts `yash_syntax::source::pretty::AnnotationType` into
782 /// `annotate_snippets::AnnotationKind`.
783 ///
784 /// This implementation is only available when the `yash_syntax` crate is
785 /// built with the `annotate-snippets` feature enabled.
786 #[allow(deprecated)]
787 impl From<AnnotationType> for annotate_snippets::AnnotationKind {
788 fn from(r#type: AnnotationType) -> Self {
789 use AnnotationType::*;
790 match r#type {
791 Error | Warning => Self::Primary,
792 Info | Note | Help => Self::Context,
793 }
794 }
795 }
796
797 /// Converts `yash_syntax::source::pretty::Message` into
798 /// `annotate_snippets::Group`.
799 ///
800 /// This implementation is only available when the `yash_syntax` crate is
801 /// built with the `annotate-snippets` feature enabled.
802 #[allow(deprecated)]
803 impl<'a> From<&'a Message<'a>> for annotate_snippets::Group<'a> {
804 fn from(message: &'a Message<'a>) -> Self {
805 let mut snippets: Vec<(
806 &super::super::Code,
807 annotate_snippets::Snippet<'a, annotate_snippets::Annotation<'a>>,
808 Vec<annotate_snippets::Annotation>,
809 )> = Vec::new();
810 // We basically convert each annotation into a snippet, but want to merge annotations
811 // with the same code into a single snippet. For this, we first collect all annotations
812 // into a temporary vector, and then merge annotations with the same code into a single
813 // snippet.
814 for annotation in &message.annotations {
815 let range = annotation.location.byte_range();
816 let as_annotation = annotate_snippets::AnnotationKind::from(annotation.r#type)
817 .span(range)
818 .label(&annotation.label);
819 let code = &*annotation.location.code;
820 if let Some((_, _, annotations)) =
821 snippets.iter_mut().find(|&&mut (c, _, _)| c == code)
822 {
823 annotations.push(as_annotation);
824 } else {
825 let line_start = code
826 .start_line_number
827 .get()
828 .try_into()
829 .unwrap_or(usize::MAX);
830 let snippet = annotate_snippets::Snippet::source(&**annotation.code)
831 .line_start(line_start)
832 .path(code.source.label());
833 snippets.push((code, snippet, vec![as_annotation]));
834 }
835 }
836
837 annotate_snippets::Level::from(message.r#type)
838 .primary_title(&*message.title)
839 .elements(
840 snippets
841 .into_iter()
842 .map(|(_, snippet, annotations)| snippet.annotations(annotations)),
843 )
844 .elements(message.footers.iter().map(|footer| {
845 annotate_snippets::Level::from(footer.r#type).message(&*footer.label)
846 }))
847 }
848 }
849}