// Diagnostic system
//
// Copyright (C) 2014-2023 Ryan Specialty, 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.
#[macro_use]
pub mod panic;
mod report;
mod resolve;
pub use report::{Reporter, VisualReporter};
pub use resolve::FsSpanResolver;
use core::fmt;
use std::{borrow::Cow, convert::Infallible, error::Error, fmt::Display};
use crate::span::Span;
/// No annotated description is applicable for the diagnostic message.
///
/// The system should strive to give the user as much relevant information
/// as is useful to resolve the problem.
/// Whether or not the absence of this description represents a deficiency
/// depends on the error context.
pub const NO_DESC: Vec = vec![];
/// 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;
}
impl Diagnostic for Infallible {
fn describe(&self) -> Vec {
// This should never actually happen unless someone is explicitly
// invoking this method on `Infallible`.
unreachable!("Infallible is not supposed to fail")
}
}
/// Diagnostic severity level.
///
/// Levels are used both for entire reports and for styling of individual
/// [`AnnotatedSpan`]s.
///
/// Higher severity levels are represented by lower integer values
/// (e.g. levelĀ 1 is the worst),
/// like DEFCON levels.
/// The rationale here is that,
/// provided that you remember that these are 1-indexed,
/// you do not need to know how many levels exist to know how severe it
/// is.
#[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 Level {
/// Whether this error level represents an error.
pub fn is_error(self) -> bool {
self <= Self::Error
}
}
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