// Diagnostic system rendering
//
// Copyright (C) 2014-2021 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 .
//! Rendering of diagnostic report.
//!
//! This module is responsible for [resolving](super::resolve) and
//! rendering spans into a formatted [`Report`],
//! which in turn can be rendered into a string with [`Display::fmt`].
//!
//! See the [parent module](super) for more summary information.
// NB: `write!` together with `\n` is preferred to `writeln!` so that there
// is only a single sequence of characters to search for while tracking
// down newlines,
// rather than using both.
use super::{
resolve::{
Column, ResolvedSpan, ResolvedSpanData, SourceLine, SpanResolver,
SpanResolverError,
},
AnnotatedSpan, Diagnostic, Label, Level,
};
use crate::span::{Context, Span, UNKNOWN_SPAN};
use std::{
fmt::{self, Display, Write},
num::NonZeroU32,
ops::Add,
};
/// Render a [`Report`] with detailed diagnostic information.
///
/// Rendering of reports is layered---this
/// report can be further rendered into a string using [`Display::fmt`].
pub trait Reporter {
/// Render diagnostic report.
///
/// The provided [`Report`] implements [`Display`].
///
/// Please be mindful of where this report is being rendered to
/// (via [`Display`]).
/// For example,
/// if rendering to standard out,
/// it is a good idea to buffer the entire report before flushing to
/// stdout,
/// otherwise the report may become interleaved with other
/// concurrent processes
/// (e.g. if TAMER is being invoked using `make -jN`).
///
/// It is also important to note that this method
/// _does not return [`Result`]_ and should never fail,
/// unless due to a panic in the standard library
/// (e.g. due to allocation failure).
/// The report absorbs errors during processing and renders those errors
/// to the report itself,
/// ensuring both that the user is made aware of the problem
/// and that we're not inadvertently suppressing the actual
/// diagnostic messages that were requested.
fn render<'d, D: Diagnostic>(&mut self, diagnostic: &'d D)
-> Report<'d, D>;
}
/// Render diagnostic report in a highly visual way.
///
/// This report is modeled after Rust's default error reporting,
/// most notable for including sections of source code associated with
/// spans,
/// underlining spans,
/// and including helpful information that walks the user through
/// understanding why the error occurred and how to approach resolving
/// it.
pub struct VisualReporter {
resolver: R,
}
impl VisualReporter {
pub fn new(resolver: R) -> Self {
Self { resolver }
}
}
impl Reporter for VisualReporter {
fn render<'d, D: Diagnostic>(
&mut self,
diagnostic: &'d D,
) -> Report<'d, D> {
let mspans =
describe_resolved(|span| self.resolver.resolve(span), diagnostic);
let mut report = Report::empty(Message(diagnostic));
report.extend(mspans.map(Into::into));
// Make each section's gutters the same size,
// which is more aesthetically pleasing.
report.normalize_gutters();
report
}
}
/// Request a diagnostic description and immediately resolve the provided
/// [`AnnotatedSpan`]s into [`MaybeResolvedSpan`]s.
///
/// Adjacent identical [`Span`]s are elided such that the first one is
/// resolved but the second one produces [`MaybeResolvedSpan::Elided`];
/// this avoids the expensive column resolution and source line
/// allocation for a span that was just processed and whose section will
/// be squashed anyway
/// (see [`Section::maybe_squash_into`]).
fn describe_resolved<'d, D, F>(
mut resolve: F,
diagnostic: &'d D,
) -> impl Iterator>
where
D: Diagnostic,
F: FnMut(Span) -> Result,
{
diagnostic.describe().into_iter().scan(
UNKNOWN_SPAN,
move |prev_span, AnnotatedSpan(span, level, olabel)| {
// Avoid re-resolving
// (and allocating memory for the source lines of)
// a span that was just resolved,
// which will just be squashed with the previous anyway.
if *prev_span == span {
return Some(MaybeResolvedSpan::Elided(span, level, olabel));
}
*prev_span = span;
Some(match resolve(span) {
Ok(rspan) => MaybeResolvedSpan::Resolved(rspan, level, olabel),
Err(e) => MaybeResolvedSpan::Unresolved(span, level, olabel, e),
})
},
)
}
/// A [`Span`] that may have been resolved.
///
/// The span will remain unresolved if an error occurred,
/// in which case the error will be provided.
/// The idea is to provide as much fallback information as is useful to the
/// user so that they can still debug the problem without the benefit of
/// the resolved context.
///
/// Furthermore,
/// it is important that the underlying diagnostic message
/// (e.g. error)
/// never be masked by an error of our own.
#[derive(Debug, PartialEq, Eq)]
enum MaybeResolvedSpan<'d, S: ResolvedSpanData> {
Resolved(S, Level, Option