tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
//! Rendering of diagnostic information.
|
|
|
|
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
use super::{
|
|
|
|
AnnotatedSpan, Diagnostic, Label, Level, ResolvedSpan, SpanResolver,
|
|
|
|
};
|
2022-04-20 12:08:46 -04:00
|
|
|
use crate::span::{Span, SpanOffsetSize, UNKNOWN_SPAN};
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
use std::fmt::{self, Write};
|
|
|
|
|
|
|
|
pub trait Reporter {
|
|
|
|
/// Render diagnostic report.
|
|
|
|
///
|
|
|
|
/// Please be mindful of where this report is being rendered `to`.
|
|
|
|
/// 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`).
|
|
|
|
fn render(
|
|
|
|
&mut self,
|
|
|
|
diagnostic: &impl Diagnostic,
|
|
|
|
to: &mut impl Write,
|
|
|
|
) -> Result<(), fmt::Error>;
|
|
|
|
|
|
|
|
/// Render a diagnostic report into an owned [`String`].
|
|
|
|
///
|
|
|
|
/// This invokes [`Reporter::render`] on a newly allocated [`String`].
|
|
|
|
fn render_to_string(
|
|
|
|
&mut self,
|
|
|
|
diagnostic: &impl Diagnostic,
|
|
|
|
) -> Result<String, fmt::Error> {
|
|
|
|
let mut str = String::new();
|
|
|
|
self.render(diagnostic, &mut str)?;
|
|
|
|
Ok(str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
2022-04-20 12:08:46 -04:00
|
|
|
pub struct VisualReporter<R: SpanResolver> {
|
|
|
|
resolver: R,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<R: SpanResolver> VisualReporter<R> {
|
|
|
|
pub fn new(resolver: R) -> Self {
|
|
|
|
Self { resolver }
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Render a raw [`Span`] that could not be resolved into a [`ResolvedSpan`].
|
|
|
|
///
|
|
|
|
/// This is not ideal,
|
|
|
|
/// but provides reasonable fallback information in a situation where
|
|
|
|
/// the diagnostic system fails.
|
|
|
|
/// The user still has enough information to diagnose the problem,
|
|
|
|
/// albeit presented in a significantly less friendly way.
|
|
|
|
fn render_fallback_span_offset(
|
|
|
|
to: &mut impl Write,
|
|
|
|
span: Span,
|
|
|
|
) -> fmt::Result {
|
|
|
|
writeln!(
|
|
|
|
to,
|
|
|
|
" offset {}--{}",
|
|
|
|
span.offset(),
|
|
|
|
span.offset() + span.len() as SpanOffsetSize
|
|
|
|
)
|
|
|
|
}
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
|
2022-04-20 12:08:46 -04:00
|
|
|
fn render_label(
|
|
|
|
to: &mut impl Write,
|
|
|
|
level: Level,
|
|
|
|
label: Label,
|
|
|
|
) -> fmt::Result {
|
|
|
|
writeln!(to, " {level}: {label}")
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
|
|
|
|
/// Attempt to render column offset.
|
|
|
|
///
|
|
|
|
/// The happy path simply outputs `":N\n"`,
|
|
|
|
/// where `N` is the column number.
|
|
|
|
///
|
|
|
|
/// If the column is not available,
|
|
|
|
/// then the line did not contain valid UTF-8.
|
|
|
|
/// In this case,
|
|
|
|
/// raw relative byte offsets are output along with help information
|
|
|
|
/// notifying the user of the issue;
|
|
|
|
/// this is hopefully enough information to quickly diagnose the
|
|
|
|
/// problem.
|
|
|
|
fn render_col(to: &mut impl Write, rspan: ResolvedSpan) -> fmt::Result {
|
|
|
|
let span = rspan.span;
|
|
|
|
|
2022-04-22 16:50:16 -04:00
|
|
|
match rspan.col_num() {
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
Some(col) => writeln!(to, ":{}", col)?,
|
|
|
|
|
|
|
|
// The column is unavailable,
|
|
|
|
// which means that the line must have contained invalid UTF-8.
|
|
|
|
// Output what we can in an attempt to help the user debug.
|
|
|
|
None => {
|
2022-04-22 16:50:16 -04:00
|
|
|
let rel = span
|
|
|
|
.relative_to(rspan.first_line_span())
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
.unwrap_or(UNKNOWN_SPAN);
|
|
|
|
|
|
|
|
writeln!(
|
|
|
|
to,
|
|
|
|
" bytes {}--{}",
|
|
|
|
rel.offset(),
|
|
|
|
rel.endpoints_saturated().1.offset()
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Self::render_label(
|
|
|
|
to,
|
|
|
|
Level::Help,
|
|
|
|
"unable to calculate columns because the line is \
|
|
|
|
not a valid UTF-8 string"
|
|
|
|
.into(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Self::render_label(
|
|
|
|
to,
|
|
|
|
Level::Help,
|
|
|
|
"you have been provided with 0-indexed \
|
|
|
|
line-relative inclusive byte offsets"
|
|
|
|
.into(),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
2022-04-20 12:08:46 -04:00
|
|
|
impl<R: SpanResolver> Reporter for VisualReporter<R> {
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
// _TODO: This is a proof-of-concept._
|
|
|
|
fn render(
|
|
|
|
&mut self,
|
|
|
|
diagnostic: &impl Diagnostic,
|
|
|
|
to: &mut impl Write,
|
2022-04-20 12:08:46 -04:00
|
|
|
) -> fmt::Result {
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
// TODO: not only errors; get the max level from the annotated spans
|
|
|
|
writeln!(to, "error: {}", diagnostic)?;
|
|
|
|
|
|
|
|
let mut prev_span = UNKNOWN_SPAN;
|
|
|
|
|
|
|
|
for AnnotatedSpan(span, level, olabel) in diagnostic.describe() {
|
|
|
|
if span != prev_span {
|
2022-04-20 12:08:46 -04:00
|
|
|
write!(to, " --> {}", span.ctx(),)?;
|
|
|
|
|
|
|
|
match self.resolver.resolve(span) {
|
|
|
|
// We should never mask an error with our own;
|
|
|
|
// the diagnostic system is supposed to _help_ the
|
|
|
|
// user in diagnosing problems,
|
|
|
|
// not hinder them by masking it.
|
|
|
|
Err(e) => {
|
|
|
|
Self::render_fallback_span_offset(to, span)?;
|
|
|
|
|
|
|
|
// Let the user know that something bad happened,
|
|
|
|
// even though this probably won't make any sense.
|
|
|
|
Self::render_label(
|
|
|
|
to,
|
|
|
|
Level::Help,
|
|
|
|
format!(
|
|
|
|
"there was an error trying to look up \
|
|
|
|
information about this span: {e}"
|
|
|
|
)
|
|
|
|
.into(),
|
|
|
|
)?;
|
|
|
|
}
|
2022-04-22 16:50:16 -04:00
|
|
|
Ok(rspan) => {
|
|
|
|
write!(to, ":{}", rspan.line_num())?;
|
|
|
|
Self::render_col(to, rspan)?;
|
|
|
|
}
|
2022-04-20 12:08:46 -04:00
|
|
|
}
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(label) = olabel {
|
2022-04-20 12:08:46 -04:00
|
|
|
Self::render_label(to, level, label)?;
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
prev_span = span;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
2022-04-22 09:21:18 -04:00
|
|
|
mod integration;
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|