mirror of https://github.com/zkat/miette.git
feat(protocol) overhauled entire protocol to be based on byte offsets (#1)
Includes other improvements! BREAKING CHANGE: Yeah this is pretty much a rewrite.
This commit is contained in:
parent
3b3a07bcb5
commit
6b51694733
|
|
@ -12,6 +12,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
indenter = "0.3.3"
|
||||
thiserror = "1.0.26"
|
||||
|
||||
[dev-dependencies]
|
||||
thiserror = "1.0.26"
|
||||
|
|
|
|||
29
README.md
29
README.md
|
|
@ -6,7 +6,7 @@ you FAIL miette? you fail her compiler like the unsafe C program? oh! oh! jail f
|
|||
|
||||
Here's an example of using something like `thisdiagnostic` to define Diagnostics declaratively.
|
||||
|
||||
```rust
|
||||
```ignore
|
||||
use thiserror::Error;
|
||||
use thisdiagnostic::Diagnostic;
|
||||
|
||||
|
|
@ -40,24 +40,27 @@ pub enum MyDiagnostic {
|
|||
)]
|
||||
#[error("Tried to add a {bad_type} to a {good_type}")]
|
||||
BadArithmetic {
|
||||
src: PathBuf,
|
||||
other_src: PathBuf,
|
||||
|
||||
// Regular error metadata for programmatic use.
|
||||
good_type: Type,
|
||||
bad_type: Type,
|
||||
bad_var: Var,
|
||||
|
||||
#[span_source(src)]
|
||||
#[label("This is a {bad_type}")]
|
||||
bad_var_span: SourceSpan,
|
||||
// Anything implementing the Source trait can be used as a source.
|
||||
src: PathBuf,
|
||||
other_src: String,
|
||||
|
||||
#[span_source(src)]
|
||||
#[label("This is a {good_type}")]
|
||||
// The context is the span of code that will be rendered.
|
||||
// There can be multiple contexts in a Diagnostic.
|
||||
#[context(src, "This region is where things went wrong.")]
|
||||
ctx: SourceSpan,
|
||||
|
||||
// Highlights underline and label specific subsections of the context.
|
||||
#[highlight(ctx, "This is a {bad_type}")]
|
||||
bad_var_span: SourceSpan, // These can span multiple lines!
|
||||
|
||||
// They can be optional!
|
||||
#[highlight(ctx, "This is a {good_type}")]
|
||||
good_var_span: Option<SourceSpan>,
|
||||
|
||||
#[span(other_src)]
|
||||
#[label("{bad_var} is defined here")]
|
||||
bad_var_definition_span: SourceSpan, // multiline span
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*!
|
||||
Iterate over error `.source()` chains.
|
||||
|
||||
NOTE: This module is taken wholesale from https://crates.io/crates/eyre.
|
||||
NOTE: This module is taken wholesale from <https://crates.io/crates/eyre>.
|
||||
*/
|
||||
use std::error::Error as StdError;
|
||||
use std::vec;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
pub use std::io;
|
||||
|
||||
pub use thiserror::Error;
|
||||
|
||||
/**
|
||||
Error enum for miette. Used by certain operations in the protocol.
|
||||
*/
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MietteError {
|
||||
#[error(transparent)]
|
||||
IoError(#[from] io::Error),
|
||||
#[error("The given offset is outside the bounds of its Source")]
|
||||
OutOfBounds
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub use chain::*;
|
||||
pub use error::*;
|
||||
pub use protocol::*;
|
||||
pub use reporter::*;
|
||||
|
||||
mod chain;
|
||||
mod error;
|
||||
mod source_impls;
|
||||
mod protocol;
|
||||
mod reporter;
|
||||
|
|
|
|||
150
src/protocol.rs
150
src/protocol.rs
|
|
@ -1,5 +1,12 @@
|
|||
/*!
|
||||
This module defines the core of the miette protocol: a series of types and traits
|
||||
that you can implement to get access to miette's (and related library's) full
|
||||
reporting and such features.
|
||||
*/
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::io::{self, Read};
|
||||
|
||||
use crate::MietteError;
|
||||
|
||||
/**
|
||||
Adds rich metadata to your Error that can be used by [DiagnosticReporter] to print
|
||||
|
|
@ -13,7 +20,7 @@ pub trait Diagnostic: std::error::Error + Send + Sync + 'static {
|
|||
/// `E0123` or Enums will work just fine.
|
||||
fn code(&self) -> &(dyn Display + 'static);
|
||||
|
||||
/// Diagnostic severity. This may be used by [Reporter]s to change the
|
||||
/// Diagnostic severity. This may be used by [DiagnosticReporter]s to change the
|
||||
/// display format of this diagnostic.
|
||||
fn severity(&self) -> Severity;
|
||||
|
||||
|
|
@ -23,9 +30,9 @@ pub trait Diagnostic: std::error::Error + Send + Sync + 'static {
|
|||
None
|
||||
}
|
||||
|
||||
/// Additional contextual details. This is typically used for adding
|
||||
/// Additional contextual snippets. This is typically used for adding
|
||||
/// marked-up source file output the way compilers often do.
|
||||
fn details(&self) -> Option<&[DiagnosticDetail]> {
|
||||
fn snippets(&self) -> Option<&[DiagnosticSnippet]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -69,52 +76,137 @@ pub enum Severity {
|
|||
}
|
||||
|
||||
/**
|
||||
Represents a readable source of some sort: a source file, a String, etc.
|
||||
Represents a readable source of some sort.
|
||||
|
||||
This trait is able to support simple Source types like [String]s, as well
|
||||
as more involved types like indexes into centralized `SourceMap`-like types,
|
||||
file handles, and even network streams.
|
||||
|
||||
If you can read it, you can source it,
|
||||
and it's not necessary to read the whole thing--meaning you should be able to
|
||||
support Sources which are gigabytes or larger in size.
|
||||
*/
|
||||
pub trait Source: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get a `Read`er from a given [Source].
|
||||
fn open(&self) -> io::Result<Box<dyn Read>>;
|
||||
/// Read the bytes for a specific span from this Source.
|
||||
fn read_span<'a>(&'a self, span: &SourceSpan)
|
||||
-> Result<Box<dyn SpanContents<'a> + '_>, MietteError>;
|
||||
}
|
||||
|
||||
/**
|
||||
Details and additional context to be displayed.
|
||||
Contents of a [Source] covered by [SourceSpan].
|
||||
|
||||
Includes line and column information to optimize highlight calculations.
|
||||
*/
|
||||
pub trait SpanContents<'a> {
|
||||
/// Reference to the data inside the associated span, in bytes.
|
||||
fn data(&self) -> &[u8];
|
||||
/// The 0-indexed line in the associated [Source] where the data begins.
|
||||
fn line(&self) -> usize;
|
||||
/// The 0-indexed column in the associated [Source] where the data begins,
|
||||
/// relative to `line`.
|
||||
fn column(&self) -> usize;
|
||||
}
|
||||
|
||||
/**
|
||||
Basic implementation of the [SpanContents] trait, for convenience.
|
||||
*/
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MietteSpanContents<'a> {
|
||||
/// Data from a [Source], in bytes.
|
||||
data: &'a [u8],
|
||||
// The 0-indexed line where the associated [SourceSpan] _starts_.
|
||||
line: usize,
|
||||
// The 0-indexed column where the associated [SourceSpan] _starts_.
|
||||
column: usize,
|
||||
}
|
||||
|
||||
impl<'a> MietteSpanContents<'a> {
|
||||
/// Make a new [MietteSpanContents] object.
|
||||
pub fn new(data: &'a [u8], line: usize, column: usize) -> MietteSpanContents<'a> {
|
||||
MietteSpanContents { data, line, column }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||
fn data(&self) -> &[u8] {
|
||||
self.data
|
||||
}
|
||||
fn line(&self) -> usize {
|
||||
self.line
|
||||
}
|
||||
fn column(&self) -> usize {
|
||||
self.column
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A snippet from a [Source] to be displayed with a message and possibly some highlights.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct DiagnosticDetail {
|
||||
/// Explanation of this specific diagnostic detail.
|
||||
pub struct DiagnosticSnippet {
|
||||
/// Explanation of this specific diagnostic snippet.
|
||||
pub message: Option<String>,
|
||||
/// The "filename" for this diagnostic.
|
||||
/// The "filename" for this snippet.
|
||||
pub source_name: String,
|
||||
/// A [Source] that can be used to read the actual text of a source.
|
||||
pub source: Box<dyn Source>,
|
||||
/// The primary [SourceSpan] where this diagnostic is located.
|
||||
pub span: SourceSpan,
|
||||
/// Additional [SourceSpan]s that can add secondary context.
|
||||
pub other_spans: Option<Vec<SourceSpan>>,
|
||||
pub context: SourceSpan,
|
||||
/// Additional [SourceSpan]s that mark specific sections of the span, for
|
||||
/// example, to underline specific text within the larger span. They're
|
||||
/// paired with labels that should be applied to those sections.
|
||||
pub highlights: Option<Vec<(String, SourceSpan)>>,
|
||||
}
|
||||
|
||||
/**
|
||||
Span within a [Source] with an associated message.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SourceSpan {
|
||||
/// A name for the thing this SourceSpan is actually pointing to.
|
||||
pub label: String,
|
||||
/// The start of the span.
|
||||
pub start: SourceLocation,
|
||||
/// The end of the span. Optional
|
||||
pub end: Option<SourceLocation>,
|
||||
pub start: SourceOffset,
|
||||
/// The (exclusive) end of the span.
|
||||
pub end: SourceOffset,
|
||||
}
|
||||
|
||||
impl SourceSpan {
|
||||
pub fn new(start: SourceOffset, end: SourceOffset) -> Self {
|
||||
assert!(
|
||||
start.offset() <= end.offset(),
|
||||
"Starting offset must come before the end offset."
|
||||
);
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.end.offset() - self.start.offset() + 1
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.start.offset() == self.end.offset()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Specific location in a [SourceSpan]
|
||||
"Raw" type for the byte offset from the beginning of a [Source].
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct SourceLocation {
|
||||
/// 0-indexed column of location.
|
||||
pub column: usize,
|
||||
/// 0-indexed line of location.
|
||||
pub line: usize,
|
||||
/// 0-indexed _character_ offset of location.
|
||||
pub offset: usize,
|
||||
pub type ByteOffset = usize;
|
||||
|
||||
/**
|
||||
Newtype that represents the [ByteOffset] from the beginning of a [Source]
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SourceOffset(ByteOffset);
|
||||
|
||||
impl SourceOffset {
|
||||
/// Actual byte offset.
|
||||
pub fn offset(&self) -> ByteOffset {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteOffset> for SourceOffset {
|
||||
fn from(bytes: ByteOffset) -> Self {
|
||||
SourceOffset(bytes)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
137
src/reporter.rs
137
src/reporter.rs
|
|
@ -2,56 +2,104 @@
|
|||
Basic reporter for Diagnostics. Probably good enough for most use-cases,
|
||||
but largely meant to be an example.
|
||||
*/
|
||||
use std::fmt;
|
||||
|
||||
use indenter::indented;
|
||||
|
||||
use crate::chain::Chain;
|
||||
use crate::protocol::{Diagnostic, DiagnosticDetail, DiagnosticReporter, Severity};
|
||||
use crate::protocol::{Diagnostic, DiagnosticSnippet, DiagnosticReporter, Severity};
|
||||
|
||||
pub struct Reporter;
|
||||
/**
|
||||
Reference implementation of the [DiagnosticReporter] trait. This is generally
|
||||
good enough for simple use-cases, but you might want to implement your own if
|
||||
you want custom reporting for your tool or app.
|
||||
*/
|
||||
pub struct MietteReporter;
|
||||
|
||||
impl Reporter {
|
||||
fn render_detail(
|
||||
&self,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
detail: &DiagnosticDetail,
|
||||
) -> core::fmt::Result {
|
||||
use core::fmt::Write as _;
|
||||
write!(f, "\n[{}]", detail.source_name)?;
|
||||
if let Some(msg) = &detail.message {
|
||||
impl MietteReporter {
|
||||
fn render_snippet(&self, f: &mut fmt::Formatter<'_>, snippet: &DiagnosticSnippet) -> fmt::Result {
|
||||
use fmt::Write as _;
|
||||
write!(f, "\n[{}]", snippet.source_name)?;
|
||||
if let Some(msg) = &snippet.message {
|
||||
write!(f, " {}:", msg)?;
|
||||
}
|
||||
writeln!(
|
||||
indented(f),
|
||||
"\n\n({}) @ line {}, col {} ",
|
||||
detail.span.label,
|
||||
detail.span.start.line + 1,
|
||||
detail.span.start.column + 1
|
||||
)?;
|
||||
if let Some(other_spans) = &detail.other_spans {
|
||||
for span in other_spans {
|
||||
writeln!(
|
||||
indented(f),
|
||||
"\n{} @ line {}, col {} ",
|
||||
span.label,
|
||||
span.start.line + 1,
|
||||
span.start.column + 1
|
||||
)?;
|
||||
writeln!(f)?;
|
||||
writeln!(f)?;
|
||||
let context_data = snippet
|
||||
.source
|
||||
.read_span(&snippet.context)
|
||||
.map_err(|_| fmt::Error)?;
|
||||
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
|
||||
let mut line = context_data.line();
|
||||
let mut column = context_data.column();
|
||||
let mut offset = snippet.context.start.offset();
|
||||
let mut line_offset = offset;
|
||||
let mut iter = context.chars().peekable();
|
||||
let mut line_str = String::new();
|
||||
let highlights = snippet.highlights.as_ref();
|
||||
while let Some(char) = iter.next() {
|
||||
offset += char.len_utf8();
|
||||
match char {
|
||||
'\r' => {
|
||||
if iter.next_if_eq(&'\n').is_some() {
|
||||
offset += 1;
|
||||
line += 1;
|
||||
column = 0;
|
||||
} else {
|
||||
line_str.push(char);
|
||||
column += 1;
|
||||
}
|
||||
}
|
||||
'\n' => {
|
||||
line += 1;
|
||||
column = 0;
|
||||
}
|
||||
_ => {
|
||||
line_str.push(char);
|
||||
column += 1;
|
||||
}
|
||||
}
|
||||
if iter.peek().is_none() {
|
||||
line += 1;
|
||||
}
|
||||
|
||||
if column == 0 || iter.peek().is_none() {
|
||||
writeln!(indented(f), "{: <2} | {}", line, line_str)?;
|
||||
line_str.clear();
|
||||
if let Some(highlights) = highlights {
|
||||
for (label, span) in highlights {
|
||||
if span.start.offset() >= line_offset && span.end.offset() < offset {
|
||||
// Highlight only covers one line.
|
||||
write!(indented(f), "{: <2} | ", "⫶")?;
|
||||
write!(
|
||||
f,
|
||||
"{}{} ",
|
||||
" ".repeat(span.start.offset() - line_offset),
|
||||
"^".repeat(span.len())
|
||||
)?;
|
||||
writeln!(f, "{}", label)?;
|
||||
} else if span.start.offset() < offset
|
||||
&& span.start.offset() >= line_offset
|
||||
&& span.end.offset() >= offset
|
||||
{
|
||||
// Multiline highlight.
|
||||
todo!("Multiline highlights.");
|
||||
}
|
||||
}
|
||||
}
|
||||
line_offset = offset;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DiagnosticReporter for Reporter {
|
||||
fn debug(
|
||||
&self,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
use core::fmt::Write as _;
|
||||
impl DiagnosticReporter for MietteReporter {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use fmt::Write as _;
|
||||
|
||||
if f.alternate() {
|
||||
return core::fmt::Debug::fmt(diagnostic, f);
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
||||
let sev = match diagnostic.severity() {
|
||||
|
|
@ -75,10 +123,10 @@ impl DiagnosticReporter for Reporter {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(details) = diagnostic.details() {
|
||||
if let Some(snippets) = diagnostic.snippets() {
|
||||
writeln!(f)?;
|
||||
for detail in details {
|
||||
self.render_detail(f, detail)?;
|
||||
for snippet in snippets {
|
||||
self.render_snippet(f, snippet)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,16 +141,13 @@ impl DiagnosticReporter for Reporter {
|
|||
}
|
||||
}
|
||||
|
||||
/// Literally what it says on the tin.
|
||||
pub struct JokeReporter;
|
||||
|
||||
impl DiagnosticReporter for JokeReporter {
|
||||
fn debug(
|
||||
&self,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return core::fmt::Debug::fmt(diagnostic, f);
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
||||
let sev = match diagnostic.severity() {
|
||||
|
|
@ -124,8 +169,8 @@ impl DiagnosticReporter for JokeReporter {
|
|||
f,
|
||||
"miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!",
|
||||
diagnostic.code(),
|
||||
diagnostic.details().map(|details| {
|
||||
details.iter().map(|detail| detail.message.clone()).collect::<Option<Vec<String>>>()
|
||||
diagnostic.snippets().map(|snippets| {
|
||||
snippets.iter().map(|snippet| snippet.message.clone()).collect::<Option<Vec<String>>>()
|
||||
}).flatten().map(|x| x.join(", ")).unwrap_or_else(||"try and cause miette to panic".into())
|
||||
)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,82 @@
|
|||
/*!
|
||||
Default trait implementations for [Source].
|
||||
*/
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read, Result};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::{Source};
|
||||
use crate::{MietteError, MietteSpanContents, Source, SourceSpan, SpanContents};
|
||||
|
||||
impl Source for String {
|
||||
fn open(&self) -> Result<Box<dyn Read>> {
|
||||
Ok(Box::new(Cursor::new(self.clone())))
|
||||
fn read_span(&self, span: &SourceSpan) -> Result<Box<dyn SpanContents + '_>, MietteError> {
|
||||
let mut offset = 0usize;
|
||||
let mut start_line = 0usize;
|
||||
let mut start_column = 0usize;
|
||||
let mut iter = self.chars().peekable();
|
||||
while let Some(char) = iter.next() {
|
||||
if offset < span.start.offset() {
|
||||
match char {
|
||||
'\r' => {
|
||||
if iter.next_if_eq(&'\n').is_some() {
|
||||
offset += 1;
|
||||
}
|
||||
start_line += 1;
|
||||
start_column = 0;
|
||||
}
|
||||
'\n' => {
|
||||
start_line += 1;
|
||||
start_column = 0;
|
||||
}
|
||||
_ => {
|
||||
start_column += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if offset >= span.end.offset() {
|
||||
return Ok(Box::new(MietteSpanContents::new(
|
||||
&self.as_bytes()[span.start.offset()..=span.end.offset()],
|
||||
start_line,
|
||||
start_column,
|
||||
)));
|
||||
}
|
||||
|
||||
offset += char.len_utf8();
|
||||
}
|
||||
Err(MietteError::OutOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for Path {
|
||||
fn open(&self) -> Result<Box<dyn Read>> {
|
||||
Ok(Box::new(File::open(self)?))
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl Source for PathBuf {
|
||||
fn open(&self) -> Result<Box<dyn Read>> {
|
||||
Ok(Box::new(File::open(self)?))
|
||||
#[test]
|
||||
fn basic() -> Result<(), MietteError> {
|
||||
let src = String::from("foo\n");
|
||||
let contents = src.read_span(&SourceSpan {
|
||||
start: 0.into(),
|
||||
end: 3.into(),
|
||||
})?;
|
||||
assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn middle() -> Result<(), MietteError> {
|
||||
let src = String::from("foo\nbar\nbaz\n");
|
||||
let contents = src.read_span(&SourceSpan {
|
||||
start: 4.into(),
|
||||
end: 7.into(),
|
||||
})?;
|
||||
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_crlf() -> Result<(), MietteError> {
|
||||
let src = String::from("foo\r\nbar\r\nbaz\r\n");
|
||||
let contents = src.read_span(&SourceSpan {
|
||||
start: 5.into(),
|
||||
end: 9.into(),
|
||||
})?;
|
||||
assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
use std::{fmt, io};
|
||||
use std::fmt;
|
||||
|
||||
use miette::{
|
||||
Diagnostic, DiagnosticDetail, DiagnosticReporter, Reporter, Severity, SourceLocation,
|
||||
SourceSpan,
|
||||
Diagnostic, DiagnosticSnippet, DiagnosticReporter, MietteError, MietteReporter, Severity, SourceSpan,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error)]
|
||||
#[error("oops!")]
|
||||
struct MyBad {
|
||||
details: Vec<DiagnosticDetail>,
|
||||
details: Vec<DiagnosticSnippet>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for MyBad {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Reporter.debug(self, f)
|
||||
MietteReporter.debug(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,13 +30,13 @@ impl Diagnostic for MyBad {
|
|||
Some(&["try doing it better next time?"])
|
||||
}
|
||||
|
||||
fn details(&self) -> Option<&[DiagnosticDetail]> {
|
||||
fn snippets(&self) -> Option<&[DiagnosticSnippet]> {
|
||||
Some(&self.details)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() -> io::Result<()> {
|
||||
fn basic() -> Result<(), MietteError> {
|
||||
let err = MyBad {
|
||||
details: Vec::new(),
|
||||
};
|
||||
|
|
@ -50,26 +49,27 @@ fn basic() -> io::Result<()> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn fancy() -> io::Result<()> {
|
||||
fn fancy() -> Result<(), MietteError> {
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
details: vec![DiagnosticDetail {
|
||||
details: vec![DiagnosticSnippet {
|
||||
message: Some("This is the part that broke".into()),
|
||||
source_name: "bad_file.rs".into(),
|
||||
source: Box::new("source_text".to_string()),
|
||||
other_spans: None,
|
||||
span: SourceSpan {
|
||||
label: "this thing here is bad".into(),
|
||||
start: SourceLocation {
|
||||
line: 0,
|
||||
column: 0,
|
||||
offset: 0,
|
||||
},
|
||||
end: None,
|
||||
source: Box::new(src.clone()),
|
||||
highlights: Some(vec![
|
||||
("this bit here".into(), SourceSpan {
|
||||
start: 9.into(),
|
||||
end: 12.into(),
|
||||
})
|
||||
]),
|
||||
context: SourceSpan {
|
||||
start: 0.into(),
|
||||
end: (src.len() - 1).into(),
|
||||
},
|
||||
}],
|
||||
};
|
||||
let out = format!("{:?}", err);
|
||||
// println!("{}", out);
|
||||
assert_eq!("Error[oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n (this thing here is bad) @ line 1, col 1 \n\n﹦try doing it better next time?\n".to_string(), out);
|
||||
assert_eq!("Error[oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 | source\n 2 | text\n ⫶ | ^^^^ this bit here\n 3 | here\n\n﹦try doing it better next time?\n".to_string(), out);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue