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.
|
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
// 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.
|
|
|
|
|
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::{
|
2022-04-28 13:24:36 -04:00
|
|
|
resolver::{
|
|
|
|
Column, ResolvedSpanData, SourceLine, SpanResolver, SpanResolverError,
|
|
|
|
},
|
2022-04-26 10:46:47 -04:00
|
|
|
AnnotatedSpan, Diagnostic, Label, Level,
|
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
|
|
|
};
|
2022-04-26 10:14:51 -04:00
|
|
|
use crate::span::{Context, Span, UNKNOWN_SPAN};
|
2022-04-27 11:27:55 -04:00
|
|
|
use std::{
|
2022-04-28 14:33:08 -04:00
|
|
|
fmt::{self, Display, Write},
|
2022-04-27 11:27:55 -04:00
|
|
|
num::NonZeroU32,
|
|
|
|
};
|
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
|
|
|
|
|
|
|
pub trait Reporter {
|
|
|
|
/// Render diagnostic report.
|
|
|
|
///
|
2022-04-27 14:58:50 -04:00
|
|
|
/// The provided [`Report`] implements [`Display`].
|
|
|
|
///
|
|
|
|
/// Please be mindful of where this report is being rendered to
|
|
|
|
/// (via [`Display`]).
|
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
|
|
|
/// 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`).
|
|
|
|
///
|
2022-04-27 14:58:50 -04:00
|
|
|
/// 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.
|
2022-04-27 15:11:29 -04:00
|
|
|
fn render<'d, D: Diagnostic>(&mut self, diagnostic: &'d D)
|
|
|
|
-> Report<'d, D>;
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 }
|
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
2022-04-20 12:08:46 -04:00
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
impl<R: SpanResolver> Reporter for VisualReporter<R> {
|
2022-04-27 15:11:29 -04:00
|
|
|
fn render<'d, D: Diagnostic>(
|
2022-04-27 15:06:57 -04:00
|
|
|
&mut self,
|
2022-04-27 15:11:29 -04:00
|
|
|
diagnostic: &'d D,
|
|
|
|
) -> Report<'d, D> {
|
2022-04-27 10:45:31 -04:00
|
|
|
// TODO: Avoid duplicate lookups of the same span,
|
|
|
|
// or at least adjacent ones.
|
|
|
|
let mspans = diagnostic
|
|
|
|
.describe()
|
|
|
|
.into_iter()
|
|
|
|
.map(|AnnotatedSpan(span, level, olabel)| {
|
|
|
|
let slabel = olabel.map(|label| SpanLabel(level, label));
|
|
|
|
|
|
|
|
match self.resolver.resolve(span) {
|
|
|
|
Ok(rspan) => MaybeResolvedSpan::Resolved(rspan, slabel),
|
|
|
|
Err(e) => MaybeResolvedSpan::Unresolved(span, slabel, e),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2022-04-27 15:06:57 -04:00
|
|
|
let mut report = Report::empty(Message(diagnostic));
|
2022-04-27 14:58:50 -04:00
|
|
|
report.extend(mspans.into_iter().map(Into::into));
|
|
|
|
report
|
2022-04-27 10:45:31 -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
|
|
|
|
2022-04-28 11:00:36 -04:00
|
|
|
/// 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, Option<SpanLabel<'d>>),
|
|
|
|
Unresolved(Span, Option<SpanLabel<'d>>, SpanResolverError),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'d, S: ResolvedSpanData> MaybeResolvedSpan<'d, S> {
|
|
|
|
/// 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.
|
2022-04-28 13:24:36 -04:00
|
|
|
fn system_lines(&self) -> Vec<SectionLine<'static>> {
|
2022-04-28 11:00:36 -04:00
|
|
|
match self {
|
|
|
|
Self::Resolved(rspan, _) if rspan.col_num().is_none() => vec![
|
2022-04-28 13:24:36 -04:00
|
|
|
SectionLine::Footnote(SpanLabel(
|
2022-04-28 11:00:36 -04:00
|
|
|
Level::Help,
|
|
|
|
"unable to calculate columns because the line is \
|
|
|
|
not a valid UTF-8 string"
|
|
|
|
.into(),
|
2022-04-28 13:24:36 -04:00
|
|
|
)),
|
|
|
|
SectionLine::Footnote(SpanLabel(
|
2022-04-28 11:00:36 -04:00
|
|
|
Level::Help,
|
|
|
|
"you have been provided with 0-indexed \
|
|
|
|
line-relative inclusive byte offsets"
|
|
|
|
.into(),
|
2022-04-28 13:24:36 -04:00
|
|
|
)),
|
2022-04-28 11:00:36 -04:00
|
|
|
],
|
|
|
|
|
|
|
|
Self::Unresolved(_, _, e) => {
|
2022-04-28 13:24:36 -04:00
|
|
|
vec![SectionLine::Footnote(SpanLabel(
|
2022-04-28 11:00:36 -04:00
|
|
|
Level::Help,
|
|
|
|
format!(
|
|
|
|
"an error occurred while trying to look up \
|
|
|
|
information about this span: {e}"
|
|
|
|
)
|
|
|
|
.into(),
|
2022-04-28 13:24:36 -04:00
|
|
|
))]
|
2022-04-28 11:00:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_ => vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
#[derive(Debug)]
|
2022-04-27 15:11:29 -04:00
|
|
|
pub struct Report<'d, D: Diagnostic> {
|
|
|
|
msg: Message<'d, D>,
|
|
|
|
secs: Vec<Section<'d>>,
|
2022-04-27 10:45:31 -04:00
|
|
|
level: Level,
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Report<'d, D> {
|
|
|
|
fn empty(msg: Message<'d, D>) -> Self {
|
2022-04-27 10:45:31 -04:00
|
|
|
Self {
|
|
|
|
msg,
|
|
|
|
secs: Vec::new(),
|
|
|
|
level: Level::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Extend<Section<'d>> for Report<'d, D> {
|
|
|
|
fn extend<T: IntoIterator<Item = Section<'d>>>(&mut self, secs: T) {
|
2022-04-27 10:45:31 -04:00
|
|
|
for sec in secs {
|
|
|
|
self.level = self.level.min(sec.level());
|
2022-04-28 10:30:04 -04:00
|
|
|
|
|
|
|
// Add the section if it cannot be squashed into the previous.
|
|
|
|
let remain = sec.maybe_squash_into(self.secs.last_mut());
|
|
|
|
self.secs.extend(remain);
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Display for Report<'d, D> {
|
2022-04-27 10:45:31 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{level}: {msg}\n", level = self.level, msg = self.msg)?;
|
2022-04-28 14:33:08 -04:00
|
|
|
|
|
|
|
self.secs.iter().try_fold(true, |first, sec| {
|
|
|
|
if !first {
|
|
|
|
f.write_char('\n')?;
|
|
|
|
}
|
|
|
|
|
|
|
|
sec.fmt(f)?;
|
|
|
|
Ok(false)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2022-04-27 15:11:29 -04:00
|
|
|
struct Message<'d, D: Diagnostic>(&'d D);
|
2022-04-27 10:45:31 -04:00
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Display for Message<'d, D> {
|
2022-04-27 15:06:57 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
Display::fmt(self.0, f)
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-28 10:30:04 -04:00
|
|
|
/// A section of a [`Report`] describing a [`Span`].
|
|
|
|
///
|
|
|
|
/// Adjacent sections describing the same [`Span`] ought to be squashed
|
|
|
|
/// (see [`Section::maybe_squash_into`]),
|
|
|
|
/// but not non-adjacent ones,
|
|
|
|
/// since reports ought to be able to produce narratives that may
|
|
|
|
/// revisit previous spans in an attempt to describe what occurred and
|
|
|
|
/// how to correct it.
|
|
|
|
///
|
|
|
|
/// Each section is delimited by a heading that provides a summary context
|
|
|
|
/// and enough of a visual distinction to be able to skim diagnostic
|
|
|
|
/// messages quickly.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
struct Section<'d> {
|
|
|
|
heading: SpanHeading,
|
|
|
|
level: Level,
|
|
|
|
span: Span,
|
2022-04-28 13:24:36 -04:00
|
|
|
body: Vec<SectionLine<'d>>,
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'s, 'd> Section<'d> {
|
2022-04-27 10:45:31 -04:00
|
|
|
fn level(&self) -> Level {
|
2022-04-28 10:30:04 -04:00
|
|
|
self.level
|
2022-04-27 10:45:31 -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
|
|
|
|
2022-04-28 10:30:04 -04:00
|
|
|
/// Squash self into the provided [`Section`] if they represent the same
|
|
|
|
/// [`Span`],
|
|
|
|
/// otherwise do nothing.
|
|
|
|
///
|
|
|
|
/// If squashed,
|
|
|
|
/// [`None`] is returned.
|
|
|
|
/// Otherwise [`Some`] is returned with `self`.
|
|
|
|
/// This return value can be used with [`Extend`] to extend a vector of
|
|
|
|
/// sections with the value after this operation.
|
|
|
|
///
|
|
|
|
/// The term "squash" is borrowed from `git rebase`.
|
|
|
|
fn maybe_squash_into(
|
|
|
|
self,
|
|
|
|
extend: Option<&mut Section<'d>>,
|
|
|
|
) -> Option<Self> {
|
|
|
|
match extend {
|
|
|
|
Some(extend_sec) if self.span == extend_sec.span => {
|
|
|
|
// TODO: At the time of writing this will cause duplication of
|
|
|
|
// system labels,
|
|
|
|
// which is not desirable.
|
2022-04-28 14:33:08 -04:00
|
|
|
extend_sec.body.extend(
|
|
|
|
// TODO: The system wastefully allocates duplicate source
|
|
|
|
// lines when resolving spans only to discard them here.
|
|
|
|
self.body
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(SectionLine::into_footnote),
|
|
|
|
);
|
|
|
|
|
2022-04-28 10:30:04 -04:00
|
|
|
None
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
2022-04-28 10:30:04 -04:00
|
|
|
|
|
|
|
_ => Some(self),
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, 'a, S> From<MaybeResolvedSpan<'d, S>> for Section<'d>
|
2022-04-27 10:45:31 -04:00
|
|
|
where
|
|
|
|
S: ResolvedSpanData,
|
|
|
|
{
|
2022-04-27 15:11:29 -04:00
|
|
|
fn from(mspan: MaybeResolvedSpan<'d, S>) -> Self {
|
2022-04-27 14:58:50 -04:00
|
|
|
let heading = SpanHeading::from(&mspan);
|
2022-04-28 14:33:08 -04:00
|
|
|
let syslines = mspan.system_lines();
|
|
|
|
|
|
|
|
let mut body = Vec::new();
|
2022-04-28 13:24:36 -04:00
|
|
|
|
|
|
|
let (span, level) = match mspan {
|
|
|
|
MaybeResolvedSpan::Resolved(rspan, oslabel) => {
|
|
|
|
let span = rspan.unresolved_span();
|
|
|
|
let src = rspan.into_lines();
|
|
|
|
|
|
|
|
let (level, mut olabel) = match oslabel {
|
|
|
|
Some(SpanLabel(level, label)) => (level, Some(label)),
|
|
|
|
None => (Default::default(), None),
|
|
|
|
};
|
|
|
|
|
|
|
|
let nlines = src.len();
|
|
|
|
|
|
|
|
body.extend(src.into_iter().enumerate().map(|(i, srcline)| {
|
|
|
|
let col = srcline.column();
|
|
|
|
|
|
|
|
SectionLine::SourceLine(SectionSourceLine {
|
|
|
|
src: srcline,
|
|
|
|
mark: LineMark {
|
|
|
|
col,
|
|
|
|
level,
|
|
|
|
label: if i == nlines - 1 {
|
|
|
|
olabel.take()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}));
|
|
|
|
|
|
|
|
(span, level)
|
|
|
|
}
|
|
|
|
MaybeResolvedSpan::Unresolved(span, olabel, _) => {
|
|
|
|
let level =
|
|
|
|
olabel.as_ref().map(SpanLabel::level).unwrap_or_default();
|
2022-04-27 14:58:50 -04:00
|
|
|
|
2022-04-28 13:24:36 -04:00
|
|
|
body.extend(olabel.map(SectionLine::Footnote));
|
|
|
|
(span, level)
|
2022-04-27 14:58:50 -04:00
|
|
|
}
|
2022-04-28 10:30:04 -04:00
|
|
|
};
|
|
|
|
|
2022-04-28 14:33:08 -04:00
|
|
|
body.extend(syslines);
|
|
|
|
|
2022-04-28 10:30:04 -04:00
|
|
|
Section {
|
|
|
|
heading,
|
|
|
|
span,
|
|
|
|
level,
|
2022-04-28 13:24:36 -04:00
|
|
|
body,
|
2022-04-27 14:58:50 -04:00
|
|
|
}
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d> Display for Section<'d> {
|
2022-04-27 10:45:31 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2022-04-28 10:30:04 -04:00
|
|
|
write!(f, " {heading}\n", heading = self.heading)?;
|
2022-04-27 10:45:31 -04:00
|
|
|
|
2022-04-28 13:24:36 -04:00
|
|
|
for line in self.body.iter() {
|
|
|
|
// Let each line have control over its own newline so that it
|
|
|
|
// can fully suppress itself if it's not relevant.
|
|
|
|
line.fmt(f)?;
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
/// Heading describing the context of a (hopefully resolved) span.
|
2022-04-26 10:14:51 -04:00
|
|
|
///
|
|
|
|
/// The ideal header contains the context along with the line, and column
|
|
|
|
/// numbers,
|
|
|
|
/// visually distinguishable from surrounding lines to allow the user to
|
|
|
|
/// quickly skip between reports.
|
2022-04-28 10:30:04 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-27 14:23:58 -04:00
|
|
|
struct SpanHeading(Context, HeadingLineNum);
|
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-27 14:23:58 -04:00
|
|
|
impl Display for SpanHeading {
|
2022-04-26 10:14:51 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let Self(ctx, line) = self;
|
2022-04-27 10:45:31 -04:00
|
|
|
write!(f, "--> {ctx}{line}")
|
2022-04-26 10:14:51 -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
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'s, 'd, S> From<&'s MaybeResolvedSpan<'d, S>> for SpanHeading
|
2022-04-26 13:18:34 -04:00
|
|
|
where
|
|
|
|
S: ResolvedSpanData,
|
|
|
|
{
|
|
|
|
/// Span header containing the (hopefully resolved) context.
|
2022-04-27 15:11:29 -04:00
|
|
|
fn from(mspan: &'s MaybeResolvedSpan<'d, S>) -> Self {
|
2022-04-26 13:18:34 -04:00
|
|
|
match mspan {
|
2022-04-27 14:23:58 -04:00
|
|
|
MaybeResolvedSpan::Resolved(rspan, _) => SpanHeading(
|
|
|
|
rspan.context(),
|
|
|
|
HeadingLineNum::Resolved(
|
|
|
|
rspan.line_num(),
|
|
|
|
rspan
|
|
|
|
.col_num()
|
|
|
|
.map(HeadingColNum::Resolved)
|
|
|
|
.unwrap_or_else(|| HeadingColNum::Unresolved {
|
|
|
|
unresolved_span: rspan.unresolved_span(),
|
|
|
|
first_line_span: rspan.first_line_span(),
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
),
|
2022-04-26 13:18:34 -04:00
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
MaybeResolvedSpan::Unresolved(span, _, _) => {
|
2022-04-27 14:23:58 -04:00
|
|
|
SpanHeading(span.context(), HeadingLineNum::Unresolved(*span))
|
2022-04-26 13:18:34 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
/// Span line number or fallback representation.
|
|
|
|
///
|
|
|
|
/// This is also responsible for attempting to produce a column number,
|
|
|
|
/// provided that a line number is available.
|
|
|
|
///
|
|
|
|
/// If a span could not be resolved,
|
|
|
|
/// offsets should be rendered in place of lines and columns.
|
2022-04-28 10:30:04 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-27 11:27:55 -04:00
|
|
|
enum HeadingLineNum {
|
2022-04-27 14:23:58 -04:00
|
|
|
Resolved(NonZeroU32, HeadingColNum),
|
2022-04-26 10:14:51 -04:00
|
|
|
Unresolved(Span),
|
|
|
|
}
|
|
|
|
|
2022-04-27 11:27:55 -04:00
|
|
|
impl Display for HeadingLineNum {
|
2022-04-26 10:14:51 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
2022-04-27 14:23:58 -04:00
|
|
|
Self::Resolved(line_num, col) => {
|
2022-04-27 11:40:46 -04:00
|
|
|
write!(f, ":{line_num}{col}")
|
2022-04-27 11:27:55 -04:00
|
|
|
}
|
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
// 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.
|
|
|
|
Self::Unresolved(span) => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
" offset {}--{}",
|
|
|
|
span.offset(),
|
|
|
|
span.endpoints_saturated().1.offset(),
|
|
|
|
)
|
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-26 10:14:51 -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
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
/// Column number or fallback representation.
|
|
|
|
///
|
|
|
|
/// If a column could not be resolved,
|
|
|
|
/// it should fall back to displaying byte offsets relative to the start
|
|
|
|
/// of the line.
|
2022-04-28 10:30:04 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-27 10:58:53 -04:00
|
|
|
enum HeadingColNum {
|
|
|
|
Resolved(Column),
|
|
|
|
Unresolved {
|
|
|
|
unresolved_span: Span,
|
|
|
|
first_line_span: Span,
|
|
|
|
},
|
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
|
2022-04-27 10:58:53 -04:00
|
|
|
impl Display for HeadingColNum {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::Resolved(col) => write!(f, ":{}", col),
|
2022-04-26 10:14:51 -04:00
|
|
|
|
|
|
|
// 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.
|
2022-04-27 10:58:53 -04:00
|
|
|
Self::Unresolved {
|
|
|
|
unresolved_span,
|
|
|
|
first_line_span,
|
|
|
|
} => {
|
|
|
|
let rel = unresolved_span
|
|
|
|
.relative_to(*first_line_span)
|
2022-04-26 10:14:51 -04:00
|
|
|
.unwrap_or(UNKNOWN_SPAN);
|
|
|
|
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
" bytes {}--{}",
|
|
|
|
rel.offset(),
|
|
|
|
rel.endpoints_saturated().1.offset()
|
|
|
|
)
|
|
|
|
}
|
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-26 10:14:51 -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
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
/// A label describing a span.
|
2022-04-27 10:45:31 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-27 15:11:29 -04:00
|
|
|
struct SpanLabel<'d>(Level, Label<'d>);
|
2022-04-26 10:14:51 -04:00
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d> SpanLabel<'d> {
|
2022-04-27 10:45:31 -04:00
|
|
|
fn level(&self) -> Level {
|
|
|
|
self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d> Display for SpanLabel<'d> {
|
2022-04-26 10:14:51 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let Self(level, label) = self;
|
2022-04-28 14:33:08 -04:00
|
|
|
write!(f, " = {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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-28 13:24:36 -04:00
|
|
|
/// A possibly-annotated line of output.
|
|
|
|
///
|
|
|
|
/// Note that a section line doesn't necessarily correspond to a single line
|
|
|
|
/// of output on a terminal;
|
|
|
|
/// lines are likely to be annotated.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
enum SectionLine<'d> {
|
|
|
|
SourceLine(SectionSourceLine<'d>),
|
|
|
|
Footnote(SpanLabel<'d>),
|
|
|
|
}
|
|
|
|
|
2022-04-28 14:33:08 -04:00
|
|
|
impl<'d> SectionLine<'d> {
|
|
|
|
fn into_footnote(self) -> Option<Self> {
|
|
|
|
match self {
|
|
|
|
Self::SourceLine(SectionSourceLine {
|
|
|
|
mark: LineMark { level, label, .. },
|
|
|
|
..
|
|
|
|
}) => label.map(|l| Self::Footnote(SpanLabel(level, l))),
|
|
|
|
|
|
|
|
Self::Footnote(..) => Some(self),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-28 13:24:36 -04:00
|
|
|
impl<'d> Display for SectionLine<'d> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::SourceLine(line) => line.fmt(f),
|
|
|
|
Self::Footnote(label) => write!(f, "{label}\n"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A line representing possibly-annotated source code.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
struct SectionSourceLine<'d> {
|
|
|
|
src: SourceLine,
|
|
|
|
mark: LineMark<'d>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'d> Display for SectionSourceLine<'d> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2022-04-28 14:33:08 -04:00
|
|
|
write!(f, " |\n")?;
|
|
|
|
write!(f, " | {src}\n", src = self.src)?;
|
|
|
|
write!(f, " |\n")?;
|
2022-04-28 13:24:36 -04:00
|
|
|
write!(f, "{}", self.mark)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A type of line annotation that marks columns and provides labels,
|
|
|
|
/// if available.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
struct LineMark<'d> {
|
|
|
|
level: Level,
|
|
|
|
col: Option<Column>,
|
|
|
|
label: Option<Label<'d>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'d> Display for LineMark<'d> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
if let Some(label) = self.label.as_ref() {
|
2022-04-28 14:33:08 -04:00
|
|
|
write!(f, " = {level}: {label}\n", level = self.level)?;
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2022-04-26 13:18:34 -04:00
|
|
|
use crate::{
|
2022-04-27 14:23:58 -04:00
|
|
|
convert::ExpectInto,
|
|
|
|
diagnose::resolver::Column,
|
|
|
|
span::{DUMMY_CONTEXT, DUMMY_SPAN},
|
2022-04-26 13:18:34 -04:00
|
|
|
};
|
2022-04-28 10:30:04 -04:00
|
|
|
use std::{io, num::NonZeroU32};
|
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-22 09:21:18 -04:00
|
|
|
mod integration;
|
2022-04-26 13:18:34 -04:00
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct StubResolvedSpan {
|
|
|
|
span: Option<Span>,
|
|
|
|
first_line_span: Option<Span>,
|
|
|
|
line_num: Option<NonZeroU32>,
|
|
|
|
col_num: Option<Column>,
|
|
|
|
context: Option<Context>,
|
2022-04-28 13:24:36 -04:00
|
|
|
src_lines: Option<Vec<SourceLine>>,
|
2022-04-26 13:18:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ResolvedSpanData for StubResolvedSpan {
|
|
|
|
fn line_num(&self) -> NonZeroU32 {
|
|
|
|
self.line_num.expect("missing stub line_num")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn col_num(&self) -> Option<Column> {
|
|
|
|
self.col_num
|
|
|
|
}
|
|
|
|
|
|
|
|
fn first_line_span(&self) -> Span {
|
|
|
|
self.first_line_span.expect("missing stub first_line_span")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn context(&self) -> Context {
|
|
|
|
self.context.expect("missing stub ctx")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unresolved_span(&self) -> Span {
|
|
|
|
self.span.expect("missing stub unresolved span")
|
|
|
|
}
|
2022-04-28 13:24:36 -04:00
|
|
|
|
|
|
|
fn into_lines(self) -> Vec<SourceLine> {
|
|
|
|
self.src_lines.unwrap_or_default()
|
|
|
|
}
|
2022-04-26 13:18:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn header_col_with_available_col() {
|
2022-04-27 10:58:53 -04:00
|
|
|
let sut = HeadingColNum::Resolved(Column::Endpoints(
|
|
|
|
5.unwrap_into(),
|
|
|
|
// Second endpoint is ignored.
|
|
|
|
6.unwrap_into(),
|
|
|
|
));
|
2022-04-26 13:18:34 -04:00
|
|
|
|
|
|
|
assert_eq!(":5", format!("{}", sut));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn header_col_without_available_col() {
|
2022-04-27 10:58:53 -04:00
|
|
|
let sut = HeadingColNum::Unresolved {
|
|
|
|
unresolved_span: DUMMY_CONTEXT.span(5, 2),
|
|
|
|
first_line_span: DUMMY_CONTEXT.span(3, 7),
|
2022-04-26 13:18:34 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(" bytes 2--4", format!("{}", sut));
|
|
|
|
}
|
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
// Note that line is coupled with `HeadingColNum`,
|
2022-04-26 13:18:34 -04:00
|
|
|
// tested above.
|
|
|
|
// The coupling is not ideal,
|
|
|
|
// but it keeps it simple and we don't concretely benefit from the
|
|
|
|
// decoupling for now.
|
|
|
|
#[test]
|
|
|
|
fn line_with_resolved_span() {
|
2022-04-27 14:23:58 -04:00
|
|
|
let sut = HeadingLineNum::Resolved(
|
|
|
|
5.unwrap_into(),
|
|
|
|
HeadingColNum::Resolved(Column::Endpoints(
|
2022-04-27 11:40:46 -04:00
|
|
|
3.unwrap_into(),
|
|
|
|
3.unwrap_into(),
|
|
|
|
)),
|
2022-04-27 14:23:58 -04:00
|
|
|
);
|
2022-04-26 13:18:34 -04:00
|
|
|
|
|
|
|
assert_eq!(":5:3", format!("{}", sut));
|
|
|
|
}
|
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
// Does _not_ use `HeadingColNum`,
|
2022-04-26 13:18:34 -04:00
|
|
|
// unlike the above,
|
|
|
|
// because the line was not resolved.
|
|
|
|
#[test]
|
|
|
|
fn line_with_unresolved_span_without_resolved_col() {
|
2022-04-27 11:27:55 -04:00
|
|
|
let sut = HeadingLineNum::Unresolved(DUMMY_CONTEXT.span(3, 4));
|
2022-04-26 13:18:34 -04:00
|
|
|
|
|
|
|
assert_eq!(" offset 3--7", format!("{}", sut));
|
|
|
|
}
|
|
|
|
|
2022-04-27 14:23:58 -04:00
|
|
|
// Whether you call this a unit or integration test depends on your
|
|
|
|
// perspective---it's
|
|
|
|
// either an integration test,
|
|
|
|
// or we're testing privates.
|
|
|
|
// Neither are ideal,
|
|
|
|
// but decoupling isn't worth the type burden that results.
|
2022-04-26 13:18:34 -04:00
|
|
|
#[test]
|
2022-04-27 10:45:31 -04:00
|
|
|
fn span_heading() {
|
2022-04-26 13:18:34 -04:00
|
|
|
let ctx = "header".unwrap_into();
|
2022-04-27 14:23:58 -04:00
|
|
|
let sut = SpanHeading(
|
|
|
|
ctx,
|
|
|
|
HeadingLineNum::Resolved(
|
|
|
|
2.unwrap_into(),
|
|
|
|
HeadingColNum::Resolved(Column::Endpoints(
|
|
|
|
6.unwrap_into(),
|
|
|
|
6.unwrap_into(),
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
);
|
2022-04-26 13:18:34 -04:00
|
|
|
|
2022-04-27 14:23:58 -04:00
|
|
|
assert_eq!("--> header:2:6", format!("{}", sut));
|
2022-04-26 13:18:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-04-28 10:30:04 -04:00
|
|
|
fn section_from_mspan_resolved() {
|
|
|
|
let ctx = Context::from("mspan/sec");
|
|
|
|
let span = ctx.span(2, 3);
|
2022-04-26 13:18:34 -04:00
|
|
|
|
2022-04-28 13:24:36 -04:00
|
|
|
let col_1 = Column::Endpoints(2.unwrap_into(), 3.unwrap_into());
|
|
|
|
let col_2 = Column::Endpoints(1.unwrap_into(), 4.unwrap_into());
|
|
|
|
|
|
|
|
let src_lines = vec![
|
|
|
|
SourceLine::new_stub(
|
|
|
|
1.unwrap_into(),
|
|
|
|
Some(col_1),
|
|
|
|
span,
|
|
|
|
"line 1".into(),
|
|
|
|
),
|
|
|
|
SourceLine::new_stub(
|
|
|
|
2.unwrap_into(),
|
|
|
|
Some(col_2),
|
|
|
|
span,
|
|
|
|
"line 2".into(),
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
2022-04-26 13:18:34 -04:00
|
|
|
assert_eq!(
|
2022-04-28 10:30:04 -04:00
|
|
|
Section::from(MaybeResolvedSpan::Resolved(
|
|
|
|
StubResolvedSpan {
|
|
|
|
context: Some(ctx),
|
|
|
|
line_num: Some(1.unwrap_into()),
|
2022-04-28 13:24:36 -04:00
|
|
|
col_num: Some(col_1),
|
2022-04-28 10:30:04 -04:00
|
|
|
first_line_span: Some(DUMMY_SPAN),
|
|
|
|
span: Some(span),
|
2022-04-28 13:24:36 -04:00
|
|
|
src_lines: Some(src_lines.clone()),
|
2022-04-28 10:30:04 -04:00
|
|
|
},
|
|
|
|
Some(SpanLabel(Level::Note, "test label".into())),
|
|
|
|
)),
|
|
|
|
Section {
|
|
|
|
heading: SpanHeading(
|
|
|
|
ctx,
|
|
|
|
HeadingLineNum::Resolved(
|
|
|
|
1.unwrap_into(),
|
|
|
|
HeadingColNum::Resolved(Column::Endpoints(
|
2022-04-27 14:23:58 -04:00
|
|
|
2.unwrap_into(),
|
|
|
|
3.unwrap_into()
|
2022-04-28 10:30:04 -04:00
|
|
|
))
|
|
|
|
)
|
|
|
|
),
|
|
|
|
span,
|
|
|
|
// Derived from label.
|
|
|
|
level: Level::Note,
|
2022-04-28 13:24:36 -04:00
|
|
|
body: vec![
|
|
|
|
SectionLine::SourceLine(SectionSourceLine {
|
|
|
|
src: src_lines[0].clone(),
|
|
|
|
mark: LineMark {
|
|
|
|
level: Level::Note,
|
|
|
|
col: Some(col_1),
|
|
|
|
// Label goes on the last source line.
|
|
|
|
label: None,
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
SectionLine::SourceLine(SectionSourceLine {
|
|
|
|
src: src_lines[1].clone(),
|
|
|
|
mark: LineMark {
|
|
|
|
level: Level::Note,
|
|
|
|
col: Some(col_2),
|
|
|
|
// Label at last source line
|
|
|
|
label: Some("test label".into()),
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
],
|
2022-04-28 10:30:04 -04:00
|
|
|
}
|
2022-04-26 13:18:34 -04:00
|
|
|
);
|
2022-04-28 10:30:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn section_from_mspan_resolved_no_label() {
|
|
|
|
let ctx = Context::from("mspan/sec-no-label");
|
|
|
|
let span = ctx.span(3, 4);
|
2022-04-26 13:18:34 -04:00
|
|
|
|
|
|
|
assert_eq!(
|
2022-04-28 10:30:04 -04:00
|
|
|
Section::from(MaybeResolvedSpan::Resolved(
|
|
|
|
StubResolvedSpan {
|
|
|
|
context: Some(ctx),
|
|
|
|
line_num: Some(2.unwrap_into()),
|
|
|
|
col_num: Some(Column::Endpoints(
|
|
|
|
1.unwrap_into(),
|
|
|
|
2.unwrap_into()
|
|
|
|
)),
|
|
|
|
first_line_span: Some(DUMMY_SPAN),
|
|
|
|
span: Some(span),
|
2022-04-28 13:24:36 -04:00
|
|
|
src_lines: None,
|
2022-04-28 10:30:04 -04:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)),
|
|
|
|
Section {
|
|
|
|
heading: SpanHeading(
|
|
|
|
ctx,
|
|
|
|
HeadingLineNum::Resolved(
|
|
|
|
2.unwrap_into(),
|
|
|
|
HeadingColNum::Resolved(Column::Endpoints(
|
|
|
|
1.unwrap_into(),
|
|
|
|
2.unwrap_into()
|
|
|
|
))
|
2022-04-27 14:23:58 -04:00
|
|
|
)
|
2022-04-28 10:30:04 -04:00
|
|
|
),
|
|
|
|
span,
|
|
|
|
// Level is normally derived from the label,
|
|
|
|
// so in this case it gets defaulted.
|
|
|
|
level: Level::default(),
|
2022-04-28 13:24:36 -04:00
|
|
|
body: vec![],
|
2022-04-28 10:30:04 -04:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn section_from_mspan_unresolved() {
|
|
|
|
let ctx = Context::from("mspan/sec-unresolved");
|
|
|
|
let span = ctx.span(2, 3);
|
|
|
|
|
|
|
|
let mspan = MaybeResolvedSpan::Unresolved::<StubResolvedSpan>(
|
|
|
|
span,
|
|
|
|
Some(SpanLabel(Level::Note, "test label".into())),
|
|
|
|
SpanResolverError::Io(io::ErrorKind::NotFound),
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Section::from(mspan),
|
|
|
|
Section {
|
|
|
|
heading: SpanHeading(ctx, HeadingLineNum::Unresolved(span),),
|
|
|
|
span,
|
|
|
|
level: Level::Note,
|
2022-04-28 13:24:36 -04:00
|
|
|
body: vec![
|
2022-04-28 14:33:08 -04:00
|
|
|
SectionLine::Footnote(SpanLabel(
|
|
|
|
Level::Note,
|
|
|
|
"test label".into()
|
|
|
|
)),
|
2022-04-28 13:24:36 -04:00
|
|
|
SectionLine::Footnote(SpanLabel(
|
|
|
|
Level::Help,
|
|
|
|
// This hard-coding is not ideal,
|
|
|
|
// as it makes the test fragile.
|
|
|
|
format!(
|
|
|
|
"an error occurred while trying to look up \
|
|
|
|
information about this span: {}",
|
|
|
|
io::ErrorKind::NotFound
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
)),
|
|
|
|
],
|
2022-04-28 10:30:04 -04:00
|
|
|
}
|
2022-04-26 13:18:34 -04:00
|
|
|
);
|
|
|
|
}
|
2022-04-28 14:33:08 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn section_footnote_into_footnote() {
|
|
|
|
assert_eq!(
|
|
|
|
SectionLine::Footnote(SpanLabel(
|
|
|
|
Level::Note,
|
|
|
|
"test footnote".into()
|
|
|
|
))
|
|
|
|
.into_footnote(),
|
|
|
|
Some(SectionLine::Footnote(SpanLabel(
|
|
|
|
Level::Note,
|
|
|
|
"test footnote".into()
|
|
|
|
))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn section_src_line_with_label_into_footnote() {
|
|
|
|
assert_eq!(
|
|
|
|
SectionLine::SourceLine(SectionSourceLine {
|
|
|
|
src: SourceLine::new_stub(
|
|
|
|
1.unwrap_into(),
|
|
|
|
None,
|
|
|
|
DUMMY_SPAN,
|
|
|
|
"discarded".into()
|
|
|
|
),
|
|
|
|
mark: LineMark {
|
|
|
|
level: Level::Help,
|
|
|
|
col: None,
|
|
|
|
label: Some("kept label".into())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.into_footnote(),
|
|
|
|
Some(SectionLine::Footnote(SpanLabel(
|
|
|
|
Level::Help,
|
|
|
|
"kept label".into()
|
|
|
|
))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn section_src_line_without_label_into_footnote() {
|
|
|
|
assert_eq!(
|
|
|
|
SectionLine::SourceLine(SectionSourceLine {
|
|
|
|
src: SourceLine::new_stub(
|
|
|
|
1.unwrap_into(),
|
|
|
|
None,
|
|
|
|
DUMMY_SPAN,
|
|
|
|
"discarded".into()
|
|
|
|
),
|
|
|
|
mark: LineMark {
|
|
|
|
level: Level::Help,
|
|
|
|
col: None,
|
|
|
|
label: None,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.into_footnote(),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Section squashing is currently only covered by integration
|
|
|
|
// tests!
|
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
|
|
|
}
|