// Diagnostic system // // Copyright (C) 2014-2022 Ryan Specialty Group, LLC. // // This file is part of TAME. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Diagnostic system for error reporting. //! //! This system is heavily motivated by Rust's. //! While the data structures and organization may differ, //! the diagnostic output is visually similar. //! //! Visual Report //! ------------- //! The primary output of the system is a [`Report`](report::Report). //! A report consists not only of the diagnostic information provided by the //! system //! (such as error messages), //! but also annotated source code. //! Reports attempt to create a narrative that walks the user through why //! and how problems arose, //! and hopefully provides information on how to resolve the problem. //! //! Here is an example report: //! //! ```text //! error: expected closing tag for `classify` //! --> /home/user/program/foo.xml:16:5 //! | //! 16 | //! | --------- note: element `classify` is opened here //! //! --> /home/user/program/foo.xml:24:5 //! | //! 24 | //! | ^^^^^^^^ error: expected `` //! ``` //! //! A single report is produced for each error //! (or other suitable diagnostic event). //! The system may produce multiple reports at a time if it discovers //! multiple issues and is able to recover enough to continue to discover //! others. //! //! Each report is separated into a number of sections, //! with each section delimited by a header. //! Each section describes one or more spans related to a range of related //! source lines. //! Those source lines are annotated using the [`Span`]s associated with the //! emitted diagnostic data, //! such as an error. //! //! Each section has a _gutter_ containing the line number of source lines. //! The area below the section header and to the right of the gutter is //! called the _body_ of the section. //! The gutter will expand as needed to fit the line number. //! //! _Warning: Reports do not yet strip terminal escape sequences, //! which may interfere with the diagnostic output. //! This may represent a security risk depending on your threat model, //! but does require access to the source code being compiled._ //! //! See the [`report`] module for more information. mod report; mod resolve; pub use report::{Reporter, VisualReporter}; pub use resolve::FsSpanResolver; use core::fmt; use std::{borrow::Cow, error::Error, fmt::Display}; use crate::span::Span; /// Diagnostic report. /// /// This describes an error condition or other special event using a series /// of [`Span`]s to describe the source, cause, and circumstances around /// an event. pub trait Diagnostic: Error + Sized { /// Produce a series of [`AnnotatedSpan`]s describing the source and /// circumstances of the diagnostic event. fn describe(&self) -> Vec; } /// Diagnostic severity level. /// /// Levels are used both for entire reports and for styling of individual /// [`AnnotatedSpan`]s. /// /// Lower levels are more severe /// (e.g. levelĀ 1 is the worst). #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] #[repr(u8)] pub enum Level { /// An error internal to TAMER that the user cannot resolve, /// but may be able to work around. InternalError = 1, /// A user-resolvable error. /// /// These represent errors resulting from the user's input. Error, /// Useful information that supplements other messages. /// /// This is most often used when multiple spans are in play for a given /// diagnostic report. Note, /// Additional advice to the user that may help in debugging or fixing a /// problem. /// /// These messages may suggest concrete fixes and are intended to /// hopefully replace having to request advice from a human. /// Unlike other severity levels which provide concrete factual /// information, /// help messages may be more speculative. #[default] Help, } impl Display for Level { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Level::InternalError => write!(f, "internal error"), Level::Error => write!(f, "error"), Level::Note => write!(f, "note"), Level::Help => write!(f, "help"), } } } /// A label associated with a report or [`Span`]. /// /// See [`AnnotatedSpan`]. #[derive(Debug, PartialEq, Eq, Clone)] pub struct Label<'a>(Cow<'a, str>); impl<'a> Display for Label<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) } } impl<'a> From for Label<'a> { fn from(s: String) -> Self { Self(Cow::Owned(s)) } } impl<'a> From<&'a String> for Label<'a> { fn from(s: &'a String) -> Self { Self(Cow::Borrowed(s)) } } impl<'a> From<&'a str> for Label<'a> { fn from(s: &'a str) -> Self { Self(Cow::Borrowed(s)) } } /// A span with an associated severity level and optional label. /// /// Annotated spans are intended to guide users through debugging a /// diagnostic message by describing important source locations that /// contribute to a given diagnostic event. #[derive(Debug, PartialEq, Eq, Clone)] pub struct AnnotatedSpan<'l>(Span, Level, Option>); impl<'l> AnnotatedSpan<'l> { pub fn with_help>>( self, label: L, ) -> [AnnotatedSpan<'l>; 2] { let span = self.0; [self, span.help(label)] } } impl<'l> From> for Vec> { fn from(x: AnnotatedSpan<'l>) -> Self { vec![x] } } pub trait Annotate: Sized { /// Annotate a [`Span`] with a severity [`Level`] and an optional /// [`Label`] to display alongside of it. /// /// You may wish to use one of the more specific methods that provide a /// more pleasent interface. fn annotate(self, level: Level, label: Option