mirror of https://github.com/zkat/miette.git
Merge branch 'main' into usize
This commit is contained in:
commit
b9da0b6558
|
|
@ -10,14 +10,12 @@ jobs:
|
||||||
name: Check fmt & build docs
|
name: Check fmt & build docs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
override: true
|
|
||||||
- name: rustfmt
|
- name: rustfmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: docs
|
- name: docs
|
||||||
|
|
@ -32,14 +30,15 @@ jobs:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
components: clippy
|
components: clippy
|
||||||
override: true
|
- name: Force older version of is-terminal for MSRV builds
|
||||||
|
if: matrix.rust == '1.56.0'
|
||||||
|
run: cargo update -p is-terminal --precise 0.4.7
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --all -- -D warnings
|
run: cargo clippy --all -- -D warnings
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
@ -54,14 +53,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
components: miri,rust-src
|
components: miri,rust-src
|
||||||
override: true
|
|
||||||
- name: Run tests with miri
|
- name: Run tests with miri
|
||||||
env:
|
env:
|
||||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
||||||
|
|
@ -75,13 +72,11 @@ jobs:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
override: true
|
|
||||||
- name: Run minimal version build
|
- name: Run minimal version build
|
||||||
run: cargo build -Z minimal-versions --all-features
|
run: cargo build -Z minimal-versions --all-features
|
||||||
|
|
||||||
|
|
|
||||||
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -1,5 +1,25 @@
|
||||||
# `miette` Release Changelog
|
# `miette` Release Changelog
|
||||||
|
|
||||||
|
<a name="5.10.0"></a>
|
||||||
|
## 5.10.0 (2023-07-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **protocol:** add StdError impl for Box<dyn Diagnostic + Send + Sync> (#273) ([2e3e5c9d](https://github.com/zkat/miette/commit/2e3e5c9d15e234495369e9b47d032644dd5664ad))
|
||||||
|
|
||||||
|
<a name="5.9.0"></a>
|
||||||
|
## 5.9.0 (2023-05-18)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **serde:** Add `serde` support (#264) ([c25676cb](https://github.com/zkat/miette/commit/c25676cb1f4266c2607836e6359f15b9cbd8637e))
|
||||||
|
* **const:** Constify various functions (#263) ([46adb3bc](https://github.com/zkat/miette/commit/46adb3bc6aa6518d82a4187b34c56e287922136f))
|
||||||
|
* **nested:** Render inner diagnostics (#170) ([aefe3237](https://github.com/zkat/miette/commit/aefe323780bda4e60feb44bb96ee98634ad677ad))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **misc:** Correct some typos (#255) ([675f3411](https://github.com/zkat/miette/commit/675f3411e33d5fae86d4018c3b72f751a4c4bc2f))
|
||||||
|
|
||||||
<a name="5.8.0"></a>
|
<a name="5.8.0"></a>
|
||||||
## 5.8.0 (2023-04-18)
|
## 5.8.0 (2023-04-18)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "miette"
|
name = "miette"
|
||||||
version = "5.8.0"
|
version = "5.10.0"
|
||||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||||
description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
|
description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
|
||||||
categories = ["rust-patterns"]
|
categories = ["rust-patterns"]
|
||||||
|
|
@ -14,7 +14,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
miette-derive = { path = "miette-derive", version = "=5.8.0" }
|
miette-derive = { path = "miette-derive", version = "=5.10.0", optional = true }
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
unicode-width = "0.1.9"
|
unicode-width = "0.1.9"
|
||||||
|
|
||||||
|
|
@ -44,7 +44,8 @@ lazy_static = "1.4"
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["derive"]
|
||||||
|
derive = ["miette-derive"]
|
||||||
no-format-args-capture = []
|
no-format-args-capture = []
|
||||||
fancy-no-backtrace = [
|
fancy-no-backtrace = [
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
|
|
|
||||||
102
README.md
102
README.md
|
|
@ -4,7 +4,7 @@
|
||||||
You run miette? You run her code like the software? Oh. Oh! Error code for
|
You run miette? You run her code like the software? Oh. Oh! Error code for
|
||||||
coder! Error code for One Thousand Lines!
|
coder! Error code for One Thousand Lines!
|
||||||
|
|
||||||
## About
|
### About
|
||||||
|
|
||||||
`miette` is a diagnostic library for Rust. It includes a series of
|
`miette` is a diagnostic library for Rust. It includes a series of
|
||||||
traits/protocols that allow you to hook into its error reporting facilities,
|
traits/protocols that allow you to hook into its error reporting facilities,
|
||||||
|
|
@ -32,7 +32,7 @@ output like in the screenshots above.** You should only do this in your
|
||||||
toplevel crate, as the fancy feature pulls in a number of dependencies that
|
toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||||
libraries and such might not want.
|
libraries and such might not want.
|
||||||
|
|
||||||
## Table of Contents <!-- omit in toc -->
|
### Table of Contents <!-- omit in toc -->
|
||||||
|
|
||||||
- [About](#about)
|
- [About](#about)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
|
|
@ -51,7 +51,7 @@ libraries and such might not want.
|
||||||
- [Acknowledgements](#acknowledgements)
|
- [Acknowledgements](#acknowledgements)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
## Features
|
### Features
|
||||||
|
|
||||||
- Generic [`Diagnostic`] protocol, compatible (and dependent on)
|
- Generic [`Diagnostic`] protocol, compatible (and dependent on)
|
||||||
[`std::error::Error`].
|
[`std::error::Error`].
|
||||||
|
|
@ -76,7 +76,7 @@ the following features:
|
||||||
- Cause chain printing
|
- Cause chain printing
|
||||||
- Turns diagnostic codes into links in [supported terminals](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
|
- Turns diagnostic codes into links in [supported terminals](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
|
||||||
|
|
||||||
## Installing
|
### Installing
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ cargo add miette
|
$ cargo add miette
|
||||||
|
|
@ -88,7 +88,7 @@ If you want to use the fancy printer in all these screenshots:
|
||||||
$ cargo add miette --features fancy
|
$ cargo add miette --features fancy
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example
|
### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
/*
|
/*
|
||||||
|
|
@ -96,7 +96,7 @@ You can derive a `Diagnostic` from any `std::error::Error` type.
|
||||||
|
|
||||||
`thiserror` is a great way to define them, and plays nicely with `miette`!
|
`thiserror` is a great way to define them, and plays nicely with `miette`!
|
||||||
*/
|
*/
|
||||||
use miette::{Diagnostic, SourceSpan};
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
|
@ -123,7 +123,7 @@ Use this `Result` type (or its expanded version) as the return type
|
||||||
throughout your app (but NOT your libraries! Those should always return
|
throughout your app (but NOT your libraries! Those should always return
|
||||||
concrete types!).
|
concrete types!).
|
||||||
*/
|
*/
|
||||||
use miette::{NamedSource, Result};
|
use miette::Result;
|
||||||
fn this_fails() -> Result<()> {
|
fn this_fails() -> Result<()> {
|
||||||
// You can use plain strings as a `Source`, or anything that implements
|
// You can use plain strings as a `Source`, or anything that implements
|
||||||
// the one-method `Source` trait.
|
// the one-method `Source` trait.
|
||||||
|
|
@ -170,9 +170,9 @@ diagnostic help: Change int or string to be the right types and try again.
|
||||||
diagnostic code: nu::parser::unsupported_operation
|
diagnostic code: nu::parser::unsupported_operation
|
||||||
For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
||||||
|
|
||||||
## Using
|
### Using
|
||||||
|
|
||||||
### ... in libraries
|
#### ... in libraries
|
||||||
|
|
||||||
`miette` is _fully compatible_ with library usage. Consumers who don't know
|
`miette` is _fully compatible_ with library usage. Consumers who don't know
|
||||||
about, or don't want, `miette` features can safely use its error types as
|
about, or don't want, `miette` features can safely use its error types as
|
||||||
|
|
@ -187,7 +187,7 @@ the trait directly, just like with `std::error::Error`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// lib/error.rs
|
// lib/error.rs
|
||||||
use miette::Diagnostic;
|
use miette::{Diagnostic, SourceSpan};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Diagnostic, Debug)]
|
#[derive(Error, Diagnostic, Debug)]
|
||||||
|
|
@ -199,6 +199,18 @@ pub enum MyLibError {
|
||||||
#[error("Oops it blew up")]
|
#[error("Oops it blew up")]
|
||||||
#[diagnostic(code(my_lib::bad_code))]
|
#[diagnostic(code(my_lib::bad_code))]
|
||||||
BadThingHappened,
|
BadThingHappened,
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
// Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
AnotherError(#[from] AnotherError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Diagnostic, Debug)]
|
||||||
|
#[error("another error")]
|
||||||
|
pub struct AnotherError {
|
||||||
|
#[label("here")]
|
||||||
|
pub at: SourceSpan
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -206,7 +218,7 @@ Then, return this error type from all your fallible public APIs. It's a best
|
||||||
practice to wrap any "external" error types in your error `enum` instead of
|
practice to wrap any "external" error types in your error `enum` instead of
|
||||||
using something like [`Report`] in a library.
|
using something like [`Report`] in a library.
|
||||||
|
|
||||||
### ... in application code
|
#### ... in application code
|
||||||
|
|
||||||
Application code tends to work a little differently than libraries. You
|
Application code tends to work a little differently than libraries. You
|
||||||
don't always need or care to define dedicated error wrappers for errors
|
don't always need or care to define dedicated error wrappers for errors
|
||||||
|
|
@ -248,8 +260,7 @@ pub fn some_tool() -> Result<Version> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To construct your own simple adhoc error use the [`miette!`] macro:
|
To construct your own simple adhoc error use the [miette!] macro:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// my_app/lib/my_internal_file.rs
|
// my_app/lib/my_internal_file.rs
|
||||||
use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
||||||
|
|
@ -262,8 +273,9 @@ pub fn some_tool() -> Result<Version> {
|
||||||
.map_err(|_| miette!("Invalid version {}", version))?)
|
.map_err(|_| miette!("Invalid version {}", version))?)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
There are also similar [bail!] and [ensure!] macros.
|
||||||
|
|
||||||
### ... in `main()`
|
#### ... in `main()`
|
||||||
|
|
||||||
`main()` is just like any other part of your application-internal code. Use
|
`main()` is just like any other part of your application-internal code. Use
|
||||||
`Result` as your return value, and it will pretty-print your diagnostics
|
`Result` as your return value, and it will pretty-print your diagnostics
|
||||||
|
|
@ -293,7 +305,24 @@ enabled:
|
||||||
miette = { version = "X.Y.Z", features = ["fancy"] }
|
miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
### ... diagnostic code URLs
|
Another way to display a diagnostic is by printing them using the debug formatter.
|
||||||
|
This is, in fact, what returning diagnostics from main ends up doing.
|
||||||
|
To do it yourself, you can write the following:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
use semver::Version;
|
||||||
|
|
||||||
|
fn just_a_random_function() {
|
||||||
|
let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
||||||
|
match version_result {
|
||||||
|
Err(e) => println!("{:?}", e),
|
||||||
|
Ok(version) => println!("{}", version),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ... diagnostic code URLs
|
||||||
|
|
||||||
`miette` supports providing a URL for individual diagnostics. This URL will
|
`miette` supports providing a URL for individual diagnostics. This URL will
|
||||||
be displayed as an actual link in supported terminals, like so:
|
be displayed as an actual link in supported terminals, like so:
|
||||||
|
|
@ -346,7 +375,7 @@ use thiserror::Error;
|
||||||
struct MyErr;
|
struct MyErr;
|
||||||
```
|
```
|
||||||
|
|
||||||
### ... snippets
|
#### ... snippets
|
||||||
|
|
||||||
Along with its general error handling and reporting features, `miette` also
|
Along with its general error handling and reporting features, `miette` also
|
||||||
includes facilities for adding error spans/annotations/labels to your
|
includes facilities for adding error spans/annotations/labels to your
|
||||||
|
|
@ -394,7 +423,7 @@ pub struct MyErrorType {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ... help text
|
##### ... help text
|
||||||
`miette` provides two facilities for supplying help text for your errors:
|
`miette` provides two facilities for supplying help text for your errors:
|
||||||
|
|
||||||
The first is the `#[help()]` format attribute that applies to structs or
|
The first is the `#[help()]` format attribute that applies to structs or
|
||||||
|
|
@ -430,7 +459,7 @@ let err = Foo {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### ... multiple related errors
|
#### ... multiple related errors
|
||||||
|
|
||||||
`miette` supports collecting multiple errors into a single diagnostic, and
|
`miette` supports collecting multiple errors into a single diagnostic, and
|
||||||
printing them all together nicely.
|
printing them all together nicely.
|
||||||
|
|
@ -450,7 +479,7 @@ struct MyError {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### ... delayed source code
|
#### ... delayed source code
|
||||||
|
|
||||||
Sometimes it makes sense to add source code to the error message later.
|
Sometimes it makes sense to add source code to the error message later.
|
||||||
One option is to use [`with_source_code()`](Report::with_source_code)
|
One option is to use [`with_source_code()`](Report::with_source_code)
|
||||||
|
|
@ -533,7 +562,7 @@ fn main() -> miette::Result<()> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### ... Diagnostic-based error sources.
|
#### ... Diagnostic-based error sources.
|
||||||
|
|
||||||
When one uses the `#[source]` attribute on a field, that usually comes
|
When one uses the `#[source]` attribute on a field, that usually comes
|
||||||
from `thiserror`, and implements a method for
|
from `thiserror`, and implements a method for
|
||||||
|
|
@ -566,7 +595,7 @@ struct MyError {
|
||||||
struct OtherError;
|
struct OtherError;
|
||||||
```
|
```
|
||||||
|
|
||||||
### ... handler options
|
#### ... handler options
|
||||||
|
|
||||||
[`MietteHandler`] is the default handler, and is very customizable. In
|
[`MietteHandler`] is the default handler, and is very customizable. In
|
||||||
most cases, you can simply use [`MietteHandlerOpts`] to tweak its behavior
|
most cases, you can simply use [`MietteHandlerOpts`] to tweak its behavior
|
||||||
|
|
@ -585,12 +614,13 @@ miette::set_hook(Box::new(|_| {
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See the docs for [`MietteHandlerOpts`] for more details on what you can
|
See the docs for [`MietteHandlerOpts`] for more details on what you can
|
||||||
customize!
|
customize!
|
||||||
|
|
||||||
### ... dynamic diagnostics
|
#### ... dynamic diagnostics
|
||||||
|
|
||||||
If you...
|
If you...
|
||||||
- ...don't know all the possible errors upfront
|
- ...don't know all the possible errors upfront
|
||||||
|
|
@ -599,9 +629,10 @@ then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||||
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
||||||
let source = "2 + 2 * 2 = 8".to_string();
|
let source = "2 + 2 * 2 = 8".to_string();
|
||||||
let report = miette!(
|
let report = miette!(
|
||||||
labels = vec[
|
labels = vec![
|
||||||
LabeledSpan::at(12..13, "this should be 6"),
|
LabeledSpan::at(12..13, "this should be 6"),
|
||||||
],
|
],
|
||||||
help = "'*' has greater precedence than '+'",
|
help = "'*' has greater precedence than '+'",
|
||||||
|
|
@ -610,26 +641,25 @@ let report = miette!(
|
||||||
println!("{:?}", report)
|
println!("{:?}", report)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Acknowledgements
|
### Acknowledgements
|
||||||
|
|
||||||
`miette` was not developed in a void. It owes enormous credit to various
|
`miette` was not developed in a void. It owes enormous credit to various
|
||||||
other projects and their authors:
|
other projects and their authors:
|
||||||
|
|
||||||
- [`anyhow`](http://crates.io/crates/anyhow) and
|
- [`anyhow`](http://crates.io/crates/anyhow) and [`color-eyre`](https://crates.io/crates/color-eyre):
|
||||||
[`color-eyre`](https://crates.io/crates/color-eyre): these two
|
these two enormously influential error handling libraries have pushed
|
||||||
enormously influential error handling libraries have pushed forward the
|
forward the experience of application-level error handling and error
|
||||||
experience of application-level error handling and error reporting.
|
reporting. `miette`'s `Report` type is an attempt at a very very rough
|
||||||
`miette`'s `Report` type is an attempt at a very very rough version of
|
version of their `Report` types.
|
||||||
their `Report` types.
|
- [`thiserror`](https://crates.io/crates/thiserror) for setting the standard
|
||||||
- [`thiserror`](https://crates.io/crates/thiserror) for setting the
|
for library-level error definitions, and for being the inspiration behind
|
||||||
standard for library-level error definitions, and for being the
|
`miette`'s derive macro.
|
||||||
inspiration behind `miette`'s derive macro.
|
|
||||||
- `rustc` and [@estebank](https://github.com/estebank) for their
|
- `rustc` and [@estebank](https://github.com/estebank) for their
|
||||||
state-of-the-art work in compiler diagnostics.
|
state-of-the-art work in compiler diagnostics.
|
||||||
- [`ariadne`](https://crates.io/crates/ariadne) for pushing forward how
|
- [`ariadne`](https://crates.io/crates/ariadne) for pushing forward how
|
||||||
_pretty_ these diagnostics can really look!
|
_pretty_ these diagnostics can really look!
|
||||||
|
|
||||||
## License
|
### License
|
||||||
|
|
||||||
`miette` is released to the Rust community under the [Apache license
|
`miette` is released to the Rust community under the [Apache license
|
||||||
2.0](./LICENSE).
|
2.0](./LICENSE).
|
||||||
|
|
@ -648,7 +678,7 @@ under the Apache License. Some code is taken from
|
||||||
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
||||||
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
||||||
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
||||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
|
[`ReportHandler`]: https://docs.rs/miette/latest/miette/trait.ReportHandler.html
|
||||||
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
||||||
[`SourceCode`]: https://docs.rs/miette/latest/miette/struct.SourceCode.html
|
[`SourceCode`]: https://docs.rs/miette/latest/miette/trait.SourceCode.html
|
||||||
[`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
|
[`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
||||||
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
||||||
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
||||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
|
[`ReportHandler`]: https://docs.rs/miette/latest/miette/trait.ReportHandler.html
|
||||||
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
||||||
[`SourceCode`]: https://docs.rs/miette/latest/miette/struct.SourceCode.html
|
[`SourceCode`]: https://docs.rs/miette/latest/miette/trait.SourceCode.html
|
||||||
[`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
|
[`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "miette-derive"
|
name = "miette-derive"
|
||||||
version = "5.8.0"
|
version = "5.10.0"
|
||||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
@ -11,6 +11,6 @@ repository = "https://github.com/zkat/miette"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0.60"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = "2.0.11"
|
syn = "2.0.11"
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,12 @@ struct Label {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
span: syn::Member,
|
span: syn::Member,
|
||||||
|
primary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LabelAttr {
|
struct LabelAttr {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
|
primary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for LabelAttr {
|
impl Parse for LabelAttr {
|
||||||
|
|
@ -40,10 +42,22 @@ impl Parse for LabelAttr {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let la = input.lookahead1();
|
let la = input.lookahead1();
|
||||||
let label = if la.peek(syn::token::Paren) {
|
let (primary, label) = if la.peek(syn::token::Paren) {
|
||||||
// #[label("{}", x)]
|
// #[label(primary?, "{}", x)]
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
|
|
||||||
|
let primary = if content.peek(syn::Ident) {
|
||||||
|
let ident: syn::Ident = content.parse()?;
|
||||||
|
if ident != "primary" {
|
||||||
|
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
|
||||||
|
}
|
||||||
|
let _ = content.parse::<Token![,]>();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
if content.peek(syn::LitStr) {
|
if content.peek(syn::LitStr) {
|
||||||
let fmt = content.parse()?;
|
let fmt = content.parse()?;
|
||||||
let args = if content.is_empty() {
|
let args = if content.is_empty() {
|
||||||
|
|
@ -56,22 +70,27 @@ impl Parse for LabelAttr {
|
||||||
args,
|
args,
|
||||||
has_bonus_display: false,
|
has_bonus_display: false,
|
||||||
};
|
};
|
||||||
Some(display)
|
(primary, Some(display))
|
||||||
|
} else if !primary {
|
||||||
|
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
|
(primary, None)
|
||||||
}
|
}
|
||||||
} else if la.peek(Token![=]) {
|
} else if la.peek(Token![=]) {
|
||||||
// #[label = "blabla"]
|
// #[label = "blabla"]
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
Some(Display {
|
(
|
||||||
fmt: input.parse()?,
|
false,
|
||||||
args: TokenStream::new(),
|
Some(Display {
|
||||||
has_bonus_display: false,
|
fmt: input.parse()?,
|
||||||
})
|
args: TokenStream::new(),
|
||||||
|
has_bonus_display: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
(false, None)
|
||||||
};
|
};
|
||||||
Ok(LabelAttr { label })
|
Ok(LabelAttr { label, primary })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,12 +119,21 @@ impl Labels {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
let LabelAttr { label } =
|
let LabelAttr { label, primary } =
|
||||||
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
||||||
|
|
||||||
|
if primary && labels.iter().any(|l: &Label| l.primary) {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
field.span(),
|
||||||
|
"Cannot have more than one primary label.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
labels.push(Label {
|
labels.push(Label {
|
||||||
label,
|
label,
|
||||||
span,
|
span,
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
|
primary,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,13 +148,23 @@ impl Labels {
|
||||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
let labels = self.0.iter().map(|highlight| {
|
let labels = self.0.iter().map(|highlight| {
|
||||||
let Label { span, label, ty } = highlight;
|
let Label {
|
||||||
|
span,
|
||||||
|
label,
|
||||||
|
ty,
|
||||||
|
primary,
|
||||||
|
} = highlight;
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
|
let ctor = if *primary {
|
||||||
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
|
} else {
|
||||||
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
|
};
|
||||||
if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
@ -134,7 +172,7 @@ impl Labels {
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
@ -161,7 +199,7 @@ impl Labels {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
labels.as_ref().and_then(|labels| {
|
labels.as_ref().and_then(|labels| {
|
||||||
let variant_labels = labels.0.iter().map(|label| {
|
let variant_labels = labels.0.iter().map(|label| {
|
||||||
let Label { span, label, ty } = label;
|
let Label { span, label, ty, primary } = label;
|
||||||
let field = match &span {
|
let field = match &span {
|
||||||
syn::Member::Named(ident) => ident.clone(),
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
|
@ -169,11 +207,16 @@ impl Labels {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
|
let ctor = if *primary {
|
||||||
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
|
} else {
|
||||||
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
|
};
|
||||||
if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
@ -181,7 +224,7 @@ impl Labels {
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use crate::{
|
||||||
|
|
||||||
pub struct SourceCode {
|
pub struct SourceCode {
|
||||||
source_code: syn::Member,
|
source_code: syn::Member,
|
||||||
|
is_option: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceCode {
|
impl SourceCode {
|
||||||
|
|
@ -27,6 +28,19 @@ impl SourceCode {
|
||||||
for (i, field) in fields.iter().enumerate() {
|
for (i, field) in fields.iter().enumerate() {
|
||||||
for attr in &field.attrs {
|
for attr in &field.attrs {
|
||||||
if attr.path().is_ident("source_code") {
|
if attr.path().is_ident("source_code") {
|
||||||
|
let is_option = if let syn::Type::Path(syn::TypePath {
|
||||||
|
path: syn::Path { segments, .. },
|
||||||
|
..
|
||||||
|
}) = &field.ty
|
||||||
|
{
|
||||||
|
segments
|
||||||
|
.last()
|
||||||
|
.map(|seg| seg.ident == "Option")
|
||||||
|
.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let source_code = if let Some(ident) = field.ident.clone() {
|
let source_code = if let Some(ident) = field.ident.clone() {
|
||||||
syn::Member::Named(ident)
|
syn::Member::Named(ident)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -35,7 +49,10 @@ impl SourceCode {
|
||||||
span: field.span(),
|
span: field.span(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
return Ok(Some(SourceCode { source_code }));
|
return Ok(Some(SourceCode {
|
||||||
|
source_code,
|
||||||
|
is_option,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,11 +62,21 @@ impl SourceCode {
|
||||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||||
let (display_pat, _display_members) = display_pat_members(fields);
|
let (display_pat, _display_members) = display_pat_members(fields);
|
||||||
let src = &self.source_code;
|
let src = &self.source_code;
|
||||||
|
let ret = if self.is_option {
|
||||||
|
quote! {
|
||||||
|
self.#src.as_ref().map(|s| s as _)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
Some(&self.#src)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
|
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
|
||||||
let Self #display_pat = self;
|
let Self #display_pat = self;
|
||||||
Some(&self.#src)
|
#ret
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +95,19 @@ impl SourceCode {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let variant_name = ident.clone();
|
let variant_name = ident.clone();
|
||||||
|
let ret = if source_code.is_option {
|
||||||
|
quote! {
|
||||||
|
#field.as_ref().map(|s| s as _)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
std::option::Option::Some(#field)
|
||||||
|
}
|
||||||
|
};
|
||||||
match &fields {
|
match &fields {
|
||||||
syn::Fields::Unit => None,
|
syn::Fields::Unit => None,
|
||||||
_ => Some(quote! {
|
_ => Some(quote! {
|
||||||
Self::#variant_name #display_pat => std::option::Option::Some(#field),
|
Self::#variant_name #display_pat => #ret,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
42
src/error.rs
42
src/error.rs
|
|
@ -1,27 +1,51 @@
|
||||||
use std::io;
|
use std::{fmt, io};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{self as miette, Diagnostic};
|
use crate::Diagnostic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Error enum for miette. Used by certain operations in the protocol.
|
Error enum for miette. Used by certain operations in the protocol.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum MietteError {
|
pub enum MietteError {
|
||||||
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
||||||
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
#[diagnostic(code(miette::io_error), url(docsrs))]
|
|
||||||
IoError(#[from] io::Error),
|
IoError(#[from] io::Error),
|
||||||
|
|
||||||
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
||||||
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
||||||
#[error("The given offset is outside the bounds of its Source")]
|
#[error("The given offset is outside the bounds of its Source")]
|
||||||
#[diagnostic(
|
|
||||||
code(miette::span_out_of_bounds),
|
|
||||||
help("Double-check your spans. Do you have an off-by-one error?"),
|
|
||||||
url(docsrs)
|
|
||||||
)]
|
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Diagnostic for MietteError {
|
||||||
|
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
|
match self {
|
||||||
|
MietteError::IoError(_) => Some(Box::new("miette::io_error")),
|
||||||
|
MietteError::OutOfBounds => Some(Box::new("miette::span_out_of_bounds")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
|
match self {
|
||||||
|
MietteError::IoError(_) => None,
|
||||||
|
MietteError::OutOfBounds => Some(Box::new(
|
||||||
|
"Double-check your spans. Do you have an off-by-one error?",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
|
let crate_version = env!("CARGO_PKG_VERSION");
|
||||||
|
let variant = match self {
|
||||||
|
MietteError::IoError(_) => "#variant.IoError",
|
||||||
|
MietteError::OutOfBounds => "#variant.OutOfBounds",
|
||||||
|
};
|
||||||
|
Some(Box::new(format!(
|
||||||
|
"https://docs.rs/miette/{}/miette/enum.MietteError.html{}",
|
||||||
|
crate_version, variant,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ impl Report {
|
||||||
|
|
||||||
/// Create a new error object from a printable error message.
|
/// Create a new error object from a printable error message.
|
||||||
///
|
///
|
||||||
/// If the argument implements std::error::Error, prefer `Report::new`
|
/// If the argument implements [`std::error::Error`], prefer `Report::new`
|
||||||
/// instead which preserves the underlying error's cause chain and
|
/// instead which preserves the underlying error's cause chain and
|
||||||
/// backtrace. If the argument may or may not implement std::error::Error
|
/// backtrace. If the argument may or may not implement [`std::error::Error`]
|
||||||
/// now or in the future, use `miette!(err)` which handles either way
|
/// now or in the future, use `miette!(err)` which handles either way
|
||||||
/// correctly.
|
/// correctly.
|
||||||
///
|
///
|
||||||
|
|
@ -206,7 +206,7 @@ impl Report {
|
||||||
/// Create a new error from an error message to wrap the existing error.
|
/// Create a new error from an error message to wrap the existing error.
|
||||||
///
|
///
|
||||||
/// For attaching a higher level error message to a `Result` as it is
|
/// For attaching a higher level error message to a `Result` as it is
|
||||||
/// propagated, the [crate::WrapErr] extension trait may be more
|
/// propagated, the [`WrapErr`](crate::WrapErr) extension trait may be more
|
||||||
/// convenient than this function.
|
/// convenient than this function.
|
||||||
///
|
///
|
||||||
/// The primary reason to use `error.wrap_err(...)` instead of
|
/// The primary reason to use `error.wrap_err(...)` instead of
|
||||||
|
|
@ -233,7 +233,7 @@ impl Report {
|
||||||
unsafe { Report::construct(error, vtable, handler) }
|
unsafe { Report::construct(error, vtable, handler) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compatibility re-export of wrap_err for interop with `anyhow`
|
/// Compatibility re-export of `wrap_err` for interop with `anyhow`
|
||||||
pub fn context<D>(self, msg: D) -> Self
|
pub fn context<D>(self, msg: D) -> Self
|
||||||
where
|
where
|
||||||
D: Display + Send + Sync + 'static,
|
D: Display + Send + Sync + 'static,
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,9 @@ macro_rules! ensure {
|
||||||
/// ## `anyhow`/`eyre` Users
|
/// ## `anyhow`/`eyre` Users
|
||||||
///
|
///
|
||||||
/// You can just replace `use`s of the `anyhow!`/`eyre!` macros with `miette!`.
|
/// You can just replace `use`s of the `anyhow!`/`eyre!` macros with `miette!`.
|
||||||
|
///
|
||||||
|
/// [`diagnostic!`]: crate::diagnostic!
|
||||||
|
/// [`Report`]: crate::Report
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! miette {
|
macro_rules! miette {
|
||||||
($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {
|
($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {
|
||||||
|
|
@ -282,6 +285,8 @@ macro_rules! miette {
|
||||||
)]
|
)]
|
||||||
/// assert_eq!(diag.message, "1 + 2 = 3");
|
/// assert_eq!(diag.message, "1 + 2 = 3");
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`MietteDiagnostic`]: crate::MietteDiagnostic
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! diagnostic {
|
macro_rules! diagnostic {
|
||||||
($fmt:literal $($arg:tt)*) => {{
|
($fmt:literal $($arg:tt)*) => {{
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ where
|
||||||
Box::from_raw(self.ptr.as_ptr())
|
Box::from_raw(self.ptr.as_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn by_ref<'a>(&self) -> Ref<'a, T> {
|
pub(crate) const fn by_ref<'a>(&self) -> Ref<'a, T> {
|
||||||
Ref {
|
Ref {
|
||||||
ptr: self.ptr,
|
ptr: self.ptr,
|
||||||
lifetime: PhantomData,
|
lifetime: PhantomData,
|
||||||
|
|
@ -91,7 +91,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_raw(ptr: NonNull<T>) -> Self {
|
pub(crate) const fn from_raw(ptr: NonNull<T>) -> Self {
|
||||||
Ref {
|
Ref {
|
||||||
ptr,
|
ptr,
|
||||||
lifetime: PhantomData,
|
lifetime: PhantomData,
|
||||||
|
|
@ -112,7 +112,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_ptr(self) -> *const T {
|
pub(crate) const fn as_ptr(self) -> *const T {
|
||||||
self.ptr.as_ptr() as *const T
|
self.ptr.as_ptr() as *const T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn by_ref(self) -> Ref<'a, T> {
|
pub(crate) const fn by_ref(self) -> Ref<'a, T> {
|
||||||
Ref {
|
Ref {
|
||||||
ptr: self.ptr,
|
ptr: self.ptr,
|
||||||
lifetime: PhantomData,
|
lifetime: PhantomData,
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ pub struct MietteHandlerOpts {
|
||||||
pub(crate) context_lines: Option<usize>,
|
pub(crate) context_lines: Option<usize>,
|
||||||
pub(crate) tab_width: Option<usize>,
|
pub(crate) tab_width: Option<usize>,
|
||||||
pub(crate) with_cause_chain: Option<bool>,
|
pub(crate) with_cause_chain: Option<bool>,
|
||||||
|
pub(crate) break_words: Option<bool>,
|
||||||
|
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||||
|
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MietteHandlerOpts {
|
impl MietteHandlerOpts {
|
||||||
|
|
@ -86,6 +89,27 @@ impl MietteHandlerOpts {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If true, long words can be broken when wrapping.
|
||||||
|
///
|
||||||
|
/// If false, long words will not be broken when they exceed the width.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
pub fn break_words(mut self, break_words: bool) -> Self {
|
||||||
|
self.break_words = Some(break_words);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `textwrap::WordSeparator` to use when determining wrap points.
|
||||||
|
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
|
||||||
|
self.word_separator = Some(word_separator);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `textwrap::WordSplitter` to use when determining wrap points.
|
||||||
|
pub fn word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
|
||||||
|
self.word_splitter = Some(word_splitter);
|
||||||
|
self
|
||||||
|
}
|
||||||
/// Include the cause chain of the top-level error in the report.
|
/// Include the cause chain of the top-level error in the report.
|
||||||
pub fn with_cause_chain(mut self) -> Self {
|
pub fn with_cause_chain(mut self) -> Self {
|
||||||
self.with_cause_chain = Some(true);
|
self.with_cause_chain = Some(true);
|
||||||
|
|
@ -233,6 +257,16 @@ impl MietteHandlerOpts {
|
||||||
if let Some(w) = self.tab_width {
|
if let Some(w) = self.tab_width {
|
||||||
handler = handler.tab_width(w);
|
handler = handler.tab_width(w);
|
||||||
}
|
}
|
||||||
|
if let Some(b) = self.break_words {
|
||||||
|
handler = handler.with_break_words(b)
|
||||||
|
}
|
||||||
|
if let Some(s) = self.word_separator {
|
||||||
|
handler = handler.with_word_separator(s)
|
||||||
|
}
|
||||||
|
if let Some(s) = self.word_splitter {
|
||||||
|
handler = handler.with_word_splitter(s)
|
||||||
|
}
|
||||||
|
|
||||||
MietteHandler {
|
MietteHandler {
|
||||||
inner: Box::new(handler),
|
inner: Box::new(handler),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct DebugReportHandler;
|
||||||
impl DebugReportHandler {
|
impl DebugReportHandler {
|
||||||
/// Create a new [`NarratableReportHandler`](crate::NarratableReportHandler)
|
/// Create a new [`NarratableReportHandler`](crate::NarratableReportHandler)
|
||||||
/// There are no customization options.
|
/// There are no customization options.
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::fmt::{self, Write};
|
||||||
use owo_colors::{OwoColorize, Style};
|
use owo_colors::{OwoColorize, Style};
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
use crate::diagnostic_chain::DiagnosticChain;
|
use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
|
||||||
use crate::handlers::theme::*;
|
use crate::handlers::theme::*;
|
||||||
use crate::protocol::{Diagnostic, Severity};
|
use crate::protocol::{Diagnostic, Severity};
|
||||||
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
@ -20,7 +20,7 @@ This printer can be customized by using [`new_themed()`](GraphicalReportHandler:
|
||||||
|
|
||||||
See [`set_hook()`](crate::set_hook) for more details on customizing your global
|
See [`set_hook()`](crate::set_hook) for more details on customizing your global
|
||||||
printer.
|
printer.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GraphicalReportHandler {
|
pub struct GraphicalReportHandler {
|
||||||
pub(crate) links: LinkStyle,
|
pub(crate) links: LinkStyle,
|
||||||
|
|
@ -30,6 +30,9 @@ pub struct GraphicalReportHandler {
|
||||||
pub(crate) context_lines: usize,
|
pub(crate) context_lines: usize,
|
||||||
pub(crate) tab_width: usize,
|
pub(crate) tab_width: usize,
|
||||||
pub(crate) with_cause_chain: bool,
|
pub(crate) with_cause_chain: bool,
|
||||||
|
pub(crate) break_words: bool,
|
||||||
|
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||||
|
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -51,6 +54,9 @@ impl GraphicalReportHandler {
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
|
break_words: true,
|
||||||
|
word_separator: None,
|
||||||
|
word_splitter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +70,9 @@ impl GraphicalReportHandler {
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
|
break_words: true,
|
||||||
|
word_separator: None,
|
||||||
|
word_splitter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,6 +131,24 @@ impl GraphicalReportHandler {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enables or disables breaking of words during wrapping.
|
||||||
|
pub fn with_break_words(mut self, break_words: bool) -> Self {
|
||||||
|
self.break_words = break_words;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the word separator to use when wrapping.
|
||||||
|
pub fn with_word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
|
||||||
|
self.word_separator = Some(word_separator);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the word splitter to usewhen wrapping.
|
||||||
|
pub fn with_word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
|
||||||
|
self.word_splitter = Some(word_splitter);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the 'global' footer for this handler.
|
/// Sets the 'global' footer for this handler.
|
||||||
pub fn with_footer(mut self, footer: String) -> Self {
|
pub fn with_footer(mut self, footer: String) -> Self {
|
||||||
self.footer = Some(footer);
|
self.footer = Some(footer);
|
||||||
|
|
@ -151,7 +178,6 @@ impl GraphicalReportHandler {
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &(dyn Diagnostic),
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
self.render_header(f, diagnostic)?;
|
self.render_header(f, diagnostic)?;
|
||||||
writeln!(f)?;
|
|
||||||
self.render_causes(f, diagnostic)?;
|
self.render_causes(f, diagnostic)?;
|
||||||
let src = diagnostic.source_code();
|
let src = diagnostic.source_code();
|
||||||
self.render_snippets(f, diagnostic, src)?;
|
self.render_snippets(f, diagnostic, src)?;
|
||||||
|
|
@ -160,9 +186,17 @@ impl GraphicalReportHandler {
|
||||||
if let Some(footer) = &self.footer {
|
if let Some(footer) = &self.footer {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
let width = self.termwidth.saturating_sub(4);
|
let width = self.termwidth.saturating_sub(4);
|
||||||
let opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(" ")
|
.initial_indent(" ")
|
||||||
.subsequent_indent(" ");
|
.subsequent_indent(" ")
|
||||||
|
.break_words(self.break_words);
|
||||||
|
if let Some(word_separator) = self.word_separator {
|
||||||
|
opts = opts.word_separator(word_separator);
|
||||||
|
}
|
||||||
|
if let Some(word_splitter) = self.word_splitter.clone() {
|
||||||
|
opts = opts.word_splitter(word_splitter);
|
||||||
|
}
|
||||||
|
|
||||||
writeln!(f, "{}", textwrap::fill(footer, opts))?;
|
writeln!(f, "{}", textwrap::fill(footer, opts))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -190,6 +224,7 @@ impl GraphicalReportHandler {
|
||||||
);
|
);
|
||||||
write!(header, "{}", link)?;
|
write!(header, "{}", link)?;
|
||||||
writeln!(f, "{}", header)?;
|
writeln!(f, "{}", header)?;
|
||||||
|
writeln!(f)?;
|
||||||
} else if let Some(code) = diagnostic.code() {
|
} else if let Some(code) = diagnostic.code() {
|
||||||
write!(header, "{}", code.style(severity_style),)?;
|
write!(header, "{}", code.style(severity_style),)?;
|
||||||
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
|
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
|
||||||
|
|
@ -197,6 +232,7 @@ impl GraphicalReportHandler {
|
||||||
write!(header, " ({})", url.style(self.theme.styles.link))?;
|
write!(header, " ({})", url.style(self.theme.styles.link))?;
|
||||||
}
|
}
|
||||||
writeln!(f, "{}", header)?;
|
writeln!(f, "{}", header)?;
|
||||||
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -211,9 +247,16 @@ impl GraphicalReportHandler {
|
||||||
let initial_indent = format!(" {} ", severity_icon.style(severity_style));
|
let initial_indent = format!(" {} ", severity_icon.style(severity_style));
|
||||||
let rest_indent = format!(" {} ", self.theme.characters.vbar.style(severity_style));
|
let rest_indent = format!(" {} ", self.theme.characters.vbar.style(severity_style));
|
||||||
let width = self.termwidth.saturating_sub(2);
|
let width = self.termwidth.saturating_sub(2);
|
||||||
let opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(&initial_indent)
|
.initial_indent(&initial_indent)
|
||||||
.subsequent_indent(&rest_indent);
|
.subsequent_indent(&rest_indent)
|
||||||
|
.break_words(self.break_words);
|
||||||
|
if let Some(word_separator) = self.word_separator {
|
||||||
|
opts = opts.word_separator(word_separator);
|
||||||
|
}
|
||||||
|
if let Some(word_splitter) = self.word_splitter.clone() {
|
||||||
|
opts = opts.word_splitter(word_splitter);
|
||||||
|
}
|
||||||
|
|
||||||
writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;
|
writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;
|
||||||
|
|
||||||
|
|
@ -250,10 +293,33 @@ impl GraphicalReportHandler {
|
||||||
)
|
)
|
||||||
.style(severity_style)
|
.style(severity_style)
|
||||||
.to_string();
|
.to_string();
|
||||||
let opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(&initial_indent)
|
.initial_indent(&initial_indent)
|
||||||
.subsequent_indent(&rest_indent);
|
.subsequent_indent(&rest_indent)
|
||||||
writeln!(f, "{}", textwrap::fill(&error.to_string(), opts))?;
|
.break_words(self.break_words);
|
||||||
|
if let Some(word_separator) = self.word_separator {
|
||||||
|
opts = opts.word_separator(word_separator);
|
||||||
|
}
|
||||||
|
if let Some(word_splitter) = self.word_splitter.clone() {
|
||||||
|
opts = opts.word_splitter(word_splitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
match error {
|
||||||
|
ErrorKind::Diagnostic(diag) => {
|
||||||
|
let mut inner = String::new();
|
||||||
|
|
||||||
|
// Don't print footer for inner errors
|
||||||
|
let mut inner_renderer = self.clone();
|
||||||
|
inner_renderer.footer = None;
|
||||||
|
inner_renderer.with_cause_chain = false;
|
||||||
|
inner_renderer.render_report(&mut inner, diag)?;
|
||||||
|
|
||||||
|
writeln!(f, "{}", textwrap::fill(&inner, opts))?;
|
||||||
|
}
|
||||||
|
ErrorKind::StdError(err) => {
|
||||||
|
writeln!(f, "{}", textwrap::fill(&err.to_string(), opts))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,9 +330,17 @@ impl GraphicalReportHandler {
|
||||||
if let Some(help) = diagnostic.help() {
|
if let Some(help) = diagnostic.help() {
|
||||||
let width = self.termwidth.saturating_sub(4);
|
let width = self.termwidth.saturating_sub(4);
|
||||||
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
|
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
|
||||||
let opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(&initial_indent)
|
.initial_indent(&initial_indent)
|
||||||
.subsequent_indent(" ");
|
.subsequent_indent(" ")
|
||||||
|
.break_words(self.break_words);
|
||||||
|
if let Some(word_separator) = self.word_separator {
|
||||||
|
opts = opts.word_separator(word_separator);
|
||||||
|
}
|
||||||
|
if let Some(word_splitter) = self.word_splitter.clone() {
|
||||||
|
opts = opts.word_splitter(word_splitter);
|
||||||
|
}
|
||||||
|
|
||||||
writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
|
writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -287,7 +361,6 @@ impl GraphicalReportHandler {
|
||||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||||
};
|
};
|
||||||
self.render_header(f, rel)?;
|
self.render_header(f, rel)?;
|
||||||
writeln!(f)?;
|
|
||||||
self.render_causes(f, rel)?;
|
self.render_causes(f, rel)?;
|
||||||
let src = rel.source_code().or(parent_src);
|
let src = rel.source_code().or(parent_src);
|
||||||
self.render_snippets(f, rel, src)?;
|
self.render_snippets(f, rel, src)?;
|
||||||
|
|
@ -367,15 +440,20 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_context<'a>(
|
fn render_context(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
source: &'a dyn SourceCode,
|
source: &dyn SourceCode,
|
||||||
context: &LabeledSpan,
|
context: &LabeledSpan,
|
||||||
labels: &[LabeledSpan],
|
labels: &[LabeledSpan],
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let (contents, lines) = self.get_lines(source, context.inner())?;
|
let (contents, lines) = self.get_lines(source, context.inner())?;
|
||||||
|
|
||||||
|
let primary_label = labels
|
||||||
|
.iter()
|
||||||
|
.find(|label| label.primary())
|
||||||
|
.or_else(|| labels.first());
|
||||||
|
|
||||||
// sorting is your friend
|
// sorting is your friend
|
||||||
let labels = labels
|
let labels = labels
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -416,19 +494,33 @@ impl GraphicalReportHandler {
|
||||||
self.theme.characters.hbar,
|
self.theme.characters.hbar,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(source_name) = contents.name() {
|
// If there is a primary label, then use its span
|
||||||
|
// as the reference point for line/column information.
|
||||||
|
let primary_contents = match primary_label {
|
||||||
|
Some(label) => source
|
||||||
|
.read_span(label.inner(), 0, 0)
|
||||||
|
.map_err(|_| fmt::Error)?,
|
||||||
|
None => contents,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(source_name) = primary_contents.name() {
|
||||||
let source_name = source_name.style(self.theme.styles.link);
|
let source_name = source_name.style(self.theme.styles.link);
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"[{}:{}:{}]",
|
"[{}:{}:{}]",
|
||||||
source_name,
|
source_name,
|
||||||
contents.line() + 1,
|
primary_contents.line() + 1,
|
||||||
contents.column() + 1
|
primary_contents.column() + 1
|
||||||
)?;
|
)?;
|
||||||
} else if lines.len() <= 1 {
|
} else if lines.len() <= 1 {
|
||||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?;
|
writeln!(
|
||||||
|
f,
|
||||||
|
"[{}:{}]",
|
||||||
|
primary_contents.line() + 1,
|
||||||
|
primary_contents.column() + 1
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now it's time for the fun part--actually rendering everything!
|
// Now it's time for the fun part--actually rendering everything!
|
||||||
|
|
@ -453,7 +545,13 @@ impl GraphicalReportHandler {
|
||||||
// no line number!
|
// no line number!
|
||||||
self.write_no_linum(f, linum_width)?;
|
self.write_no_linum(f, linum_width)?;
|
||||||
// gutter _again_
|
// gutter _again_
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
self.render_single_line_highlights(
|
self.render_single_line_highlights(
|
||||||
f,
|
f,
|
||||||
line,
|
line,
|
||||||
|
|
@ -465,11 +563,7 @@ impl GraphicalReportHandler {
|
||||||
}
|
}
|
||||||
for hl in multi_line {
|
for hl in multi_line {
|
||||||
if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) {
|
if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) {
|
||||||
// no line number!
|
self.render_multi_line_end(f, &labels, max_gutter, linum_width, line, hl)?;
|
||||||
self.write_no_linum(f, linum_width)?;
|
|
||||||
// gutter _again_
|
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
|
||||||
self.render_multi_line_end(f, hl)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -483,6 +577,91 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_multi_line_end(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
labels: &[FancySpan],
|
||||||
|
max_gutter: usize,
|
||||||
|
linum_width: usize,
|
||||||
|
line: &Line,
|
||||||
|
label: &FancySpan,
|
||||||
|
) -> fmt::Result {
|
||||||
|
// no line number!
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
|
||||||
|
if let Some(label_parts) = label.label_parts() {
|
||||||
|
// if it has a label, how long is it?
|
||||||
|
let (first, rest) = label_parts
|
||||||
|
.split_first()
|
||||||
|
.expect("cannot crash because rest would have been None, see docs on the `label` field of FancySpan");
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
first,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::MultiLineFirst,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
first,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::MultiLineFirst,
|
||||||
|
)?;
|
||||||
|
for label_line in rest {
|
||||||
|
// no line number!
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::MultiLineRest,
|
||||||
|
)?;
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
label_line,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::MultiLineRest,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
// has no label
|
||||||
|
writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn render_line_gutter(
|
fn render_line_gutter(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
|
|
@ -551,6 +730,7 @@ impl GraphicalReportHandler {
|
||||||
max_gutter: usize,
|
max_gutter: usize,
|
||||||
line: &Line,
|
line: &Line,
|
||||||
highlights: &[FancySpan],
|
highlights: &[FancySpan],
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if max_gutter == 0 {
|
if max_gutter == 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -560,15 +740,33 @@ impl GraphicalReportHandler {
|
||||||
let applicable = highlights.iter().filter(|hl| line.span_applies(hl));
|
let applicable = highlights.iter().filter(|hl| line.span_applies(hl));
|
||||||
for (i, hl) in applicable.enumerate() {
|
for (i, hl) in applicable.enumerate() {
|
||||||
if !line.span_line_only(hl) && line.span_ends(hl) {
|
if !line.span_line_only(hl) && line.span_ends(hl) {
|
||||||
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
if render_mode == LabelRenderMode::MultiLineRest {
|
||||||
gutter.push_str(
|
// this is to make multiline labels work. We want to make the right amount
|
||||||
&chars
|
// of horizontal space for them, but not actually draw the lines
|
||||||
.hbar
|
for _ in 0..max_gutter.saturating_sub(i) + 2 {
|
||||||
.to_string()
|
gutter.push(' ');
|
||||||
.repeat(max_gutter.saturating_sub(i) + 2)
|
}
|
||||||
.style(hl.style)
|
} else {
|
||||||
.to_string(),
|
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
||||||
);
|
|
||||||
|
gutter.push_str(
|
||||||
|
&chars
|
||||||
|
.hbar
|
||||||
|
.to_string()
|
||||||
|
.repeat(
|
||||||
|
max_gutter.saturating_sub(i)
|
||||||
|
// if we are rendering a multiline label, then leave a bit of space for the
|
||||||
|
// rcross character
|
||||||
|
+ if render_mode == LabelRenderMode::MultiLineFirst {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.style(hl.style)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
||||||
|
|
@ -617,11 +815,22 @@ impl GraphicalReportHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the visual column position of a byte offset on a specific line.
|
/// Returns the visual column position of a byte offset on a specific line.
|
||||||
fn visual_offset(&self, line: &Line, offset: usize) -> usize {
|
///
|
||||||
|
/// If the offset occurs in the middle of a character, the returned column
|
||||||
|
/// corresponds to that character's first column in `start` is true, or its
|
||||||
|
/// last column if `start` is false.
|
||||||
|
fn visual_offset(&self, line: &Line, offset: usize, start: bool) -> usize {
|
||||||
let line_range = line.offset..=(line.offset + line.length);
|
let line_range = line.offset..=(line.offset + line.length);
|
||||||
assert!(line_range.contains(&offset));
|
assert!(line_range.contains(&offset));
|
||||||
|
|
||||||
let text_index = offset - line.offset;
|
let mut text_index = offset - line.offset;
|
||||||
|
while text_index <= line.text.len() && !line.text.is_char_boundary(text_index) {
|
||||||
|
if start {
|
||||||
|
text_index -= 1;
|
||||||
|
} else {
|
||||||
|
text_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
let text = &line.text[..text_index.min(line.text.len())];
|
let text = &line.text[..text_index.min(line.text.len())];
|
||||||
let text_width = self.line_visual_char_width(text).sum();
|
let text_width = self.line_visual_char_width(text).sum();
|
||||||
if text_index > line.text.len() {
|
if text_index > line.text.len() {
|
||||||
|
|
@ -644,10 +853,10 @@ impl GraphicalReportHandler {
|
||||||
for (c, width) in text.chars().zip(self.line_visual_char_width(text)) {
|
for (c, width) in text.chars().zip(self.line_visual_char_width(text)) {
|
||||||
if c == '\t' {
|
if c == '\t' {
|
||||||
for _ in 0..width {
|
for _ in 0..width {
|
||||||
f.write_char(' ')?
|
f.write_char(' ')?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.write_char(c)?
|
f.write_char(c)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.write_char('\n')?;
|
f.write_char('\n')?;
|
||||||
|
|
@ -672,32 +881,34 @@ impl GraphicalReportHandler {
|
||||||
.map(|hl| {
|
.map(|hl| {
|
||||||
let byte_start = hl.offset();
|
let byte_start = hl.offset();
|
||||||
let byte_end = hl.offset() + hl.len();
|
let byte_end = hl.offset() + hl.len();
|
||||||
let start = self.visual_offset(line, byte_start).max(highest);
|
let start = self.visual_offset(line, byte_start, true).max(highest);
|
||||||
let end = self.visual_offset(line, byte_end).max(start + 1);
|
let end = if hl.len() == 0 {
|
||||||
|
start + 1
|
||||||
|
} else {
|
||||||
|
self.visual_offset(line, byte_end, false).max(start + 1)
|
||||||
|
};
|
||||||
|
|
||||||
let vbar_offset = (start + end) / 2;
|
let vbar_offset = (start + end) / 2;
|
||||||
let num_left = vbar_offset - start;
|
let num_left = vbar_offset - start;
|
||||||
let num_right = end - vbar_offset - 1;
|
let num_right = end - vbar_offset - 1;
|
||||||
if start < end {
|
underlines.push_str(
|
||||||
underlines.push_str(
|
&format!(
|
||||||
&format!(
|
"{:width$}{}{}{}",
|
||||||
"{:width$}{}{}{}",
|
"",
|
||||||
"",
|
chars.underline.to_string().repeat(num_left),
|
||||||
chars.underline.to_string().repeat(num_left),
|
if hl.len() == 0 {
|
||||||
if hl.len() == 0 {
|
chars.uarrow
|
||||||
chars.uarrow
|
} else if hl.label().is_some() {
|
||||||
} else if hl.label().is_some() {
|
chars.underbar
|
||||||
chars.underbar
|
} else {
|
||||||
} else {
|
chars.underline
|
||||||
chars.underline
|
},
|
||||||
},
|
chars.underline.to_string().repeat(num_right),
|
||||||
chars.underline.to_string().repeat(num_right),
|
width = start.saturating_sub(highest),
|
||||||
width = start.saturating_sub(highest),
|
)
|
||||||
)
|
.style(hl.style)
|
||||||
.style(hl.style)
|
.to_string(),
|
||||||
.to_string(),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
highest = std::cmp::max(highest, end);
|
highest = std::cmp::max(highest, end);
|
||||||
|
|
||||||
(hl, vbar_offset)
|
(hl, vbar_offset)
|
||||||
|
|
@ -706,27 +917,40 @@ impl GraphicalReportHandler {
|
||||||
writeln!(f, "{}", underlines)?;
|
writeln!(f, "{}", underlines)?;
|
||||||
|
|
||||||
for hl in single_liners.iter().rev() {
|
for hl in single_liners.iter().rev() {
|
||||||
if let Some(label) = hl.label() {
|
if let Some(label) = hl.label_parts() {
|
||||||
self.write_no_linum(f, linum_width)?;
|
if label.len() == 1 {
|
||||||
self.render_highlight_gutter(f, max_gutter, line, all_highlights)?;
|
self.write_label_text(
|
||||||
let mut curr_offset = 1usize;
|
f,
|
||||||
for (offset_hl, vbar_offset) in &vbar_offsets {
|
line,
|
||||||
while curr_offset < *vbar_offset + 1 {
|
linum_width,
|
||||||
write!(f, " ")?;
|
max_gutter,
|
||||||
curr_offset += 1;
|
all_highlights,
|
||||||
}
|
chars,
|
||||||
if *offset_hl != hl {
|
&vbar_offsets,
|
||||||
write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?;
|
hl,
|
||||||
curr_offset += 1;
|
&label[0],
|
||||||
} else {
|
LabelRenderMode::SingleLine,
|
||||||
let lines = format!(
|
)?;
|
||||||
"{}{} {}",
|
} else {
|
||||||
chars.lbot,
|
let mut first = true;
|
||||||
chars.hbar.to_string().repeat(2),
|
for label_line in &label {
|
||||||
label,
|
self.write_label_text(
|
||||||
);
|
f,
|
||||||
writeln!(f, "{}", lines.style(hl.style))?;
|
line,
|
||||||
break;
|
linum_width,
|
||||||
|
max_gutter,
|
||||||
|
all_highlights,
|
||||||
|
chars,
|
||||||
|
&vbar_offsets,
|
||||||
|
hl,
|
||||||
|
label_line,
|
||||||
|
if first {
|
||||||
|
LabelRenderMode::MultiLineFirst
|
||||||
|
} else {
|
||||||
|
LabelRenderMode::MultiLineRest
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -734,13 +958,80 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_multi_line_end(&self, f: &mut impl fmt::Write, hl: &FancySpan) -> fmt::Result {
|
// I know it's not good practice, but making this a function makes a lot of sense
|
||||||
writeln!(
|
// and making a struct for this does not...
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn write_label_text(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
line: &Line,
|
||||||
|
linum_width: usize,
|
||||||
|
max_gutter: usize,
|
||||||
|
all_highlights: &[FancySpan],
|
||||||
|
chars: &ThemeCharacters,
|
||||||
|
vbar_offsets: &[(&&FancySpan, usize)],
|
||||||
|
hl: &&FancySpan,
|
||||||
|
label: &str,
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
|
) -> fmt::Result {
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
self.render_highlight_gutter(
|
||||||
f,
|
f,
|
||||||
"{} {}",
|
max_gutter,
|
||||||
self.theme.characters.hbar.style(hl.style),
|
line,
|
||||||
hl.label().unwrap_or_else(|| "".into()),
|
all_highlights,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
)?;
|
)?;
|
||||||
|
let mut curr_offset = 1usize;
|
||||||
|
for (offset_hl, vbar_offset) in vbar_offsets {
|
||||||
|
while curr_offset < *vbar_offset + 1 {
|
||||||
|
write!(f, " ")?;
|
||||||
|
curr_offset += 1;
|
||||||
|
}
|
||||||
|
if *offset_hl != hl {
|
||||||
|
write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?;
|
||||||
|
curr_offset += 1;
|
||||||
|
} else {
|
||||||
|
let lines = match render_mode {
|
||||||
|
LabelRenderMode::SingleLine => format!(
|
||||||
|
"{}{} {}",
|
||||||
|
chars.lbot,
|
||||||
|
chars.hbar.to_string().repeat(2),
|
||||||
|
label,
|
||||||
|
),
|
||||||
|
LabelRenderMode::MultiLineFirst => {
|
||||||
|
format!("{}{}{} {}", chars.lbot, chars.hbar, chars.rcross, label,)
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineRest => {
|
||||||
|
format!(" {} {}", chars.vbar, label,)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeln!(f, "{}", lines.style(hl.style))?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_multi_line_end_single(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
label: &str,
|
||||||
|
style: Style,
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
|
) -> fmt::Result {
|
||||||
|
match render_mode {
|
||||||
|
LabelRenderMode::SingleLine => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.hbar.style(style), label)?;
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineFirst => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.rcross.style(style), label)?;
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineRest => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.vbar.style(style), label)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -819,6 +1110,16 @@ impl ReportHandler for GraphicalReportHandler {
|
||||||
Support types
|
Support types
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum LabelRenderMode {
|
||||||
|
/// we're rendering a single line label (or not rendering in any special way)
|
||||||
|
SingleLine,
|
||||||
|
/// we're rendering a multiline label
|
||||||
|
MultiLineFirst,
|
||||||
|
/// we're rendering the rest of a multiline label
|
||||||
|
MultiLineRest,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Line {
|
struct Line {
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
|
|
@ -836,10 +1137,10 @@ impl Line {
|
||||||
let spanlen = if span.len() == 0 { 1 } else { span.len() };
|
let spanlen = if span.len() == 0 { 1 } else { span.len() };
|
||||||
// Span starts in this line
|
// Span starts in this line
|
||||||
(span.offset() >= self.offset && span.offset() < self.offset + self.length)
|
(span.offset() >= self.offset && span.offset() < self.offset + self.length)
|
||||||
// Span passes through this line
|
// Span passes through this line
|
||||||
|| (span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length) //todo
|
|| (span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length) //todo
|
||||||
// Span ends on this line
|
// Span ends on this line
|
||||||
|| (span.offset() + spanlen > self.offset && span.offset() + spanlen <= self.offset + self.length)
|
|| (span.offset() + spanlen > self.offset && span.offset() + spanlen <= self.offset + self.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A 'flyby' is a multi-line span that technically covers this line, but
|
// A 'flyby' is a multi-line span that technically covers this line, but
|
||||||
|
|
@ -869,7 +1170,10 @@ impl Line {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct FancySpan {
|
struct FancySpan {
|
||||||
label: Option<String>,
|
/// this is deliberately an option of a vec because I wanted to be very explicit
|
||||||
|
/// that there can also be *no* label. If there is a label, it can have multiple
|
||||||
|
/// lines which is what the vec is for.
|
||||||
|
label: Option<Vec<String>>,
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
style: Style,
|
style: Style,
|
||||||
}
|
}
|
||||||
|
|
@ -880,9 +1184,17 @@ impl PartialEq for FancySpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_label(v: String) -> Vec<String> {
|
||||||
|
v.split('\n').map(|i| i.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl FancySpan {
|
impl FancySpan {
|
||||||
fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {
|
fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {
|
||||||
FancySpan { label, span, style }
|
FancySpan {
|
||||||
|
label: label.map(split_label),
|
||||||
|
span,
|
||||||
|
style,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> Style {
|
fn style(&self) -> Style {
|
||||||
|
|
@ -892,7 +1204,15 @@ impl FancySpan {
|
||||||
fn label(&self) -> Option<String> {
|
fn label(&self) -> Option<String> {
|
||||||
self.label
|
self.label
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|l| l.style(self.style()).to_string())
|
.map(|l| l.join("\n").style(self.style()).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_parts(&self) -> Option<Vec<String>> {
|
||||||
|
self.label.as_ref().map(|l| {
|
||||||
|
l.iter()
|
||||||
|
.map(|i| i.style(self.style()).to_string())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offset(&self) -> usize {
|
fn offset(&self) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct JSONReportHandler;
|
||||||
impl JSONReportHandler {
|
impl JSONReportHandler {
|
||||||
/// Create a new [`JSONReportHandler`]. There are no customization
|
/// Create a new [`JSONReportHandler`]. There are no customization
|
||||||
/// options.
|
/// options.
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ impl fmt::Display for Escape<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escape(input: &'_ str) -> Escape<'_> {
|
const fn escape(input: &'_ str) -> Escape<'_> {
|
||||||
Escape(input)
|
Escape(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ impl JSONReportHandler {
|
||||||
}
|
}
|
||||||
write!(f, r#""{}""#, escape(&error.to_string()))?;
|
write!(f, r#""{}""#, escape(&error.to_string()))?;
|
||||||
}
|
}
|
||||||
write!(f, "],")?
|
write!(f, "],")?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, r#""causes": [],"#)?;
|
write!(f, r#""causes": [],"#)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ pub struct NarratableReportHandler {
|
||||||
impl NarratableReportHandler {
|
impl NarratableReportHandler {
|
||||||
/// Create a new [`NarratableReportHandler`]. There are no customization
|
/// Create a new [`NarratableReportHandler`]. There are no customization
|
||||||
/// options.
|
/// options.
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
footer: None,
|
footer: None,
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
|
|
@ -31,13 +31,13 @@ impl NarratableReportHandler {
|
||||||
|
|
||||||
/// Include the cause chain of the top-level error in the report, if
|
/// Include the cause chain of the top-level error in the report, if
|
||||||
/// available.
|
/// available.
|
||||||
pub fn with_cause_chain(mut self) -> Self {
|
pub const fn with_cause_chain(mut self) -> Self {
|
||||||
self.with_cause_chain = true;
|
self.with_cause_chain = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do not include the cause chain of the top-level error in the report.
|
/// Do not include the cause chain of the top-level error in the report.
|
||||||
pub fn without_cause_chain(mut self) -> Self {
|
pub const fn without_cause_chain(mut self) -> Self {
|
||||||
self.with_cause_chain = false;
|
self.with_cause_chain = false;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ impl NarratableReportHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the number of lines of context to show around each error.
|
/// Sets the number of lines of context to show around each error.
|
||||||
pub fn with_context_lines(mut self, lines: usize) -> Self {
|
pub const fn with_context_lines(mut self, lines: usize) -> Self {
|
||||||
self.context_lines = lines;
|
self.context_lines = lines;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,9 @@ impl GraphicalTheme {
|
||||||
|
|
||||||
/// A "basic" graphical theme that skips colors and unicode characters and
|
/// A "basic" graphical theme that skips colors and unicode characters and
|
||||||
/// just does monochrome ascii art. If you want a completely non-graphical
|
/// just does monochrome ascii art. If you want a completely non-graphical
|
||||||
/// rendering of your `Diagnostic`s, check out
|
/// rendering of your [`Diagnostic`](crate::Diagnostic)s, check out
|
||||||
/// [crate::NarratableReportHandler], or write your own
|
/// [`NarratableReportHandler`](crate::NarratableReportHandler), or write
|
||||||
/// [crate::ReportHandler]!
|
/// your own [`ReportHandler`](crate::ReportHandler)
|
||||||
pub fn none() -> Self {
|
pub fn none() -> Self {
|
||||||
Self {
|
Self {
|
||||||
characters: ThemeCharacters::ascii(),
|
characters: ThemeCharacters::ascii(),
|
||||||
|
|
@ -79,7 +79,8 @@ impl Default for GraphicalTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Styles for various parts of graphical rendering for the [crate::GraphicalReportHandler].
|
Styles for various parts of graphical rendering for the
|
||||||
|
[`GraphicalReportHandler`](crate::GraphicalReportHandler).
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ThemeStyles {
|
pub struct ThemeStyles {
|
||||||
|
|
@ -159,7 +160,7 @@ impl ThemeStyles {
|
||||||
// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
|
// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
|
||||||
|
|
||||||
/// Characters to be used when drawing when using
|
/// Characters to be used when drawing when using
|
||||||
/// [crate::GraphicalReportHandler].
|
/// [`GraphicalReportHandler`](crate::GraphicalReportHandler).
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct ThemeCharacters {
|
pub struct ThemeCharacters {
|
||||||
|
|
|
||||||
33
src/lib.rs
33
src/lib.rs
|
|
@ -186,7 +186,7 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! // lib/error.rs
|
//! // lib/error.rs
|
||||||
//! use miette::Diagnostic;
|
//! use miette::{Diagnostic, SourceSpan};
|
||||||
//! use thiserror::Error;
|
//! use thiserror::Error;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Error, Diagnostic, Debug)]
|
//! #[derive(Error, Diagnostic, Debug)]
|
||||||
|
|
@ -198,6 +198,18 @@
|
||||||
//! #[error("Oops it blew up")]
|
//! #[error("Oops it blew up")]
|
||||||
//! #[diagnostic(code(my_lib::bad_code))]
|
//! #[diagnostic(code(my_lib::bad_code))]
|
||||||
//! BadThingHappened,
|
//! BadThingHappened,
|
||||||
|
//!
|
||||||
|
//! #[error(transparent)]
|
||||||
|
//! // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||||
|
//! #[diagnostic(transparent)]
|
||||||
|
//! AnotherError(#[from] AnotherError),
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Error, Diagnostic, Debug)]
|
||||||
|
//! #[error("another error")]
|
||||||
|
//! pub struct AnotherError {
|
||||||
|
//! #[label("here")]
|
||||||
|
//! pub at: SourceSpan
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
|
@ -292,6 +304,23 @@
|
||||||
//! miette = { version = "X.Y.Z", features = ["fancy"] }
|
//! miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Another way to display a diagnostic is by printing them using the debug formatter.
|
||||||
|
//! This is, in fact, what returning diagnostics from main ends up doing.
|
||||||
|
//! To do it yourself, you can write the following:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use miette::{IntoDiagnostic, Result};
|
||||||
|
//! use semver::Version;
|
||||||
|
//!
|
||||||
|
//! fn just_a_random_function() {
|
||||||
|
//! let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
||||||
|
//! match version_result {
|
||||||
|
//! Err(e) => println!("{:?}", e),
|
||||||
|
//! Ok(version) => println!("{}", version),
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! ### ... diagnostic code URLs
|
//! ### ... diagnostic code URLs
|
||||||
//!
|
//!
|
||||||
//! `miette` supports providing a URL for individual diagnostics. This URL will
|
//! `miette` supports providing a URL for individual diagnostics. This URL will
|
||||||
|
|
@ -581,6 +610,7 @@
|
||||||
//! .unicode(false)
|
//! .unicode(false)
|
||||||
//! .context_lines(3)
|
//! .context_lines(3)
|
||||||
//! .tab_width(4)
|
//! .tab_width(4)
|
||||||
|
//! .break_words(true)
|
||||||
//! .build(),
|
//! .build(),
|
||||||
//! )
|
//! )
|
||||||
//! }))
|
//! }))
|
||||||
|
|
@ -640,6 +670,7 @@
|
||||||
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
|
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
|
||||||
//! under the Apache License. Some code is taken from
|
//! under the Apache License. Some code is taken from
|
||||||
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
||||||
|
#[cfg(feature = "derive")]
|
||||||
pub use miette_derive::*;
|
pub use miette_derive::*;
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ impl MietteDiagnostic {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
|
pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
|
||||||
let mut all_labels = self.labels.unwrap_or_default();
|
let mut all_labels = self.labels.unwrap_or_default();
|
||||||
all_labels.extend(labels.into_iter());
|
all_labels.extend(labels);
|
||||||
self.labels = Some(all_labels);
|
self.labels = Some(all_labels);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -292,14 +292,16 @@ fn test_serialize_miette_diagnostic() {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"length": 0
|
"length": 0
|
||||||
},
|
},
|
||||||
"label": "label1"
|
"label": "label1",
|
||||||
|
"primary": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"span": {
|
"span": {
|
||||||
"offset": 1,
|
"offset": 1,
|
||||||
"length": 2
|
"length": 2
|
||||||
},
|
},
|
||||||
"label": "label2"
|
"label": "label2",
|
||||||
|
"primary": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
@ -350,14 +352,16 @@ fn test_deserialize_miette_diagnostic() {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"length": 0
|
"length": 0
|
||||||
},
|
},
|
||||||
"label": "label1"
|
"label": "label1",
|
||||||
|
"primary": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"span": {
|
"span": {
|
||||||
"offset": 1,
|
"offset": 1,
|
||||||
"length": 2
|
"length": 2
|
||||||
},
|
},
|
||||||
"label": "label2"
|
"label": "label2",
|
||||||
|
"primary": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -69,16 +69,28 @@ pub trait Diagnostic: std::error::Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Box<dyn Diagnostic> {
|
macro_rules! box_impls {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
($($box_type:ty),*) => {
|
||||||
(**self).source()
|
$(
|
||||||
}
|
impl std::error::Error for $box_type {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
(**self).source()
|
||||||
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||||
self.source()
|
self.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
box_impls! {
|
||||||
|
Box<dyn Diagnostic>,
|
||||||
|
Box<dyn Diagnostic + Send>,
|
||||||
|
Box<dyn Diagnostic + Send + Sync>
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Diagnostic + Send + Sync + 'static> From<T>
|
impl<T: Diagnostic + Send + Sync + 'static> From<T>
|
||||||
for Box<dyn Diagnostic + Send + Sync + 'static>
|
for Box<dyn Diagnostic + Send + Sync + 'static>
|
||||||
{
|
{
|
||||||
|
|
@ -220,7 +232,7 @@ whole thing--meaning you should be able to support `SourceCode`s which are
|
||||||
gigabytes or larger in size.
|
gigabytes or larger in size.
|
||||||
*/
|
*/
|
||||||
pub trait SourceCode: Send + Sync {
|
pub trait SourceCode: Send + Sync {
|
||||||
/// Read the bytes for a specific span from this SourceCode, keeping a
|
/// Read the bytes for a specific span from this `SourceCode`, keeping a
|
||||||
/// certain number of lines before and after the span as context.
|
/// certain number of lines before and after the span as context.
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|
@ -237,14 +249,16 @@ pub struct LabeledSpan {
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
label: Option<String>,
|
label: Option<String>,
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
|
primary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LabeledSpan {
|
impl LabeledSpan {
|
||||||
/// Makes a new labeled span.
|
/// Makes a new labeled span.
|
||||||
pub fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
|
pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
span: (offset, len).into(),
|
span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
|
||||||
|
primary: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,6 +267,16 @@ impl LabeledSpan {
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
span: span.into(),
|
span: span.into(),
|
||||||
|
primary: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a new labeled primary span using an existing span.
|
||||||
|
pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
span: span.into(),
|
||||||
|
primary: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,24 +334,29 @@ impl LabeledSpan {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the inner [`SourceSpan`].
|
/// Returns a reference to the inner [`SourceSpan`].
|
||||||
pub fn inner(&self) -> &SourceSpan {
|
pub const fn inner(&self) -> &SourceSpan {
|
||||||
&self.span
|
&self.span
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the 0-based starting byte offset.
|
/// Returns the 0-based starting byte offset.
|
||||||
pub fn offset(&self) -> usize {
|
pub const fn offset(&self) -> usize {
|
||||||
self.span.offset()
|
self.span.offset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of bytes this `LabeledSpan` spans.
|
/// Returns the number of bytes this `LabeledSpan` spans.
|
||||||
pub fn len(&self) -> usize {
|
pub const fn len(&self) -> usize {
|
||||||
self.span.len()
|
self.span.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if this `LabeledSpan` is empty.
|
/// True if this `LabeledSpan` is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub const fn is_empty(&self) -> bool {
|
||||||
self.span.is_empty()
|
self.span.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if this `LabeledSpan` is a primary span.
|
||||||
|
pub const fn primary(&self) -> bool {
|
||||||
|
self.primary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
@ -338,7 +367,8 @@ fn test_serialize_labeled_span() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(LabeledSpan::new(None, 0, 0)),
|
json!(LabeledSpan::new(None, 0, 0)),
|
||||||
json!({
|
json!({
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -346,9 +376,10 @@ fn test_serialize_labeled_span() {
|
||||||
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
|
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
|
||||||
json!({
|
json!({
|
||||||
"label": "label",
|
"label": "label",
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false,
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
@ -358,23 +389,26 @@ fn test_deserialize_labeled_span() {
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"label": null,
|
"label": null,
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false,
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"label": "label",
|
"label": "label",
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
|
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -422,7 +456,7 @@ pub struct MietteSpanContents<'a> {
|
||||||
|
|
||||||
impl<'a> MietteSpanContents<'a> {
|
impl<'a> MietteSpanContents<'a> {
|
||||||
/// Make a new [`MietteSpanContents`] object.
|
/// Make a new [`MietteSpanContents`] object.
|
||||||
pub fn new(
|
pub const fn new(
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
line: usize,
|
line: usize,
|
||||||
|
|
@ -440,7 +474,7 @@ impl<'a> MietteSpanContents<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
|
/// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
|
||||||
pub fn new_named(
|
pub const fn new_named(
|
||||||
name: String,
|
name: String,
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
|
|
@ -492,7 +526,7 @@ pub struct SourceSpan {
|
||||||
|
|
||||||
impl SourceSpan {
|
impl SourceSpan {
|
||||||
/// Create a new [`SourceSpan`].
|
/// Create a new [`SourceSpan`].
|
||||||
pub fn new(start: SourceOffset, length: usize) -> Self {
|
pub const fn new(start: SourceOffset, length: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
offset: start,
|
offset: start,
|
||||||
length,
|
length,
|
||||||
|
|
@ -500,18 +534,18 @@ impl SourceSpan {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
|
/// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
|
||||||
pub fn offset(&self) -> usize {
|
pub const fn offset(&self) -> usize {
|
||||||
self.offset.offset()
|
self.offset.offset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total length of the [`SourceSpan`], in bytes.
|
/// Total length of the [`SourceSpan`], in bytes.
|
||||||
pub fn len(&self) -> usize {
|
pub const fn len(&self) -> usize {
|
||||||
self.length
|
self.length
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this [`SourceSpan`] has a length of zero. It may still be useful
|
/// Whether this [`SourceSpan`] has a length of zero. It may still be useful
|
||||||
/// to point to a specific point.
|
/// to point to a specific point.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub const fn is_empty(&self) -> bool {
|
||||||
self.length == 0
|
self.length == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -563,7 +597,7 @@ fn test_serialize_source_span() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(SourceSpan::from(0)),
|
json!(SourceSpan::from(0)),
|
||||||
json!({ "offset": 0, "length": 0})
|
json!({ "offset": 0, "length": 0})
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
@ -572,7 +606,7 @@ fn test_deserialize_source_span() {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
|
let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
|
||||||
assert_eq!(span, SourceSpan::from(0))
|
assert_eq!(span, SourceSpan::from(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -589,7 +623,7 @@ pub struct SourceOffset(ByteOffset);
|
||||||
|
|
||||||
impl SourceOffset {
|
impl SourceOffset {
|
||||||
/// Actual byte offset.
|
/// Actual byte offset.
|
||||||
pub fn offset(&self) -> ByteOffset {
|
pub const fn offset(&self) -> ByteOffset {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -674,12 +708,12 @@ fn test_source_offset_from_location() {
|
||||||
fn test_serialize_source_offset() {
|
fn test_serialize_source_offset() {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
assert_eq!(json!(SourceOffset::from(0)), 0)
|
assert_eq!(json!(SourceOffset::from(0)), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_source_offset() {
|
fn test_deserialize_source_offset() {
|
||||||
let offset: SourceOffset = serde_json::from_str("0").unwrap();
|
let offset: SourceOffset = serde_json::from_str("0").unwrap();
|
||||||
assert_eq!(offset, SourceOffset::from(0))
|
assert_eq!(offset, SourceOffset::from(0));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ fn fmt_help() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1 x hello x \"2\"".to_string(),
|
"1 x hello x \"2\"".to_string(),
|
||||||
FooStruct("hello".into()).help().unwrap().to_string()
|
FooStruct("hello").help().unwrap().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -201,12 +201,7 @@ fn fmt_help() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1 x hello x \"2\"".to_string(),
|
"1 x hello x \"2\"".to_string(),
|
||||||
BarStruct {
|
BarStruct { my_field: "hello" }.help().unwrap().to_string()
|
||||||
my_field: "hello".into()
|
|
||||||
}
|
|
||||||
.help()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -224,7 +219,7 @@ fn fmt_help() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1 x bar x \"2\"".to_string(),
|
"1 x bar x \"2\"".to_string(),
|
||||||
FooEnum::X("bar".into()).help().unwrap().to_string()
|
FooEnum::X("bar").help().unwrap().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -250,12 +245,7 @@ fn help_field() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"x".to_string(),
|
"x".to_string(),
|
||||||
Foo {
|
Foo { do_this: Some("x") }.help().unwrap().to_string()
|
||||||
do_this: Some("x".into())
|
|
||||||
}
|
|
||||||
.help()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -271,16 +261,11 @@ fn help_field() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"x".to_string(),
|
"x".to_string(),
|
||||||
Bar::A(Some("x".into())).help().unwrap().to_string()
|
Bar::A(Some("x")).help().unwrap().to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"x".to_string(),
|
"x".to_string(),
|
||||||
Bar::B {
|
Bar::B { do_this: Some("x") }.help().unwrap().to_string()
|
||||||
do_this: Some("x".into())
|
|
||||||
}
|
|
||||||
.help()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -288,20 +273,14 @@ fn help_field() {
|
||||||
#[diagnostic()]
|
#[diagnostic()]
|
||||||
struct Baz<'a>(#[help] Option<&'a str>);
|
struct Baz<'a>(#[help] Option<&'a str>);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!("x".to_string(), Baz(Some("x")).help().unwrap().to_string());
|
||||||
"x".to_string(),
|
|
||||||
Baz(Some("x".into())).help().unwrap().to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic()]
|
#[diagnostic()]
|
||||||
struct Quux<'a>(#[help] &'a str);
|
struct Quux<'a>(#[help] &'a str);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!("x".to_string(), Quux("x").help().unwrap().to_string());
|
||||||
"x".to_string(),
|
|
||||||
Quux("x".into()).help().unwrap().to_string()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -589,7 +568,7 @@ fn test_unit_struct_display() {
|
||||||
#[error("unit only")]
|
#[error("unit only")]
|
||||||
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
|
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
|
||||||
struct UnitOnly;
|
struct UnitOnly;
|
||||||
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help")
|
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -603,5 +582,47 @@ fn test_unit_enum_display() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Enum::UnitVariant.help().unwrap().to_string(),
|
Enum::UnitVariant.help().unwrap().to_string(),
|
||||||
"hello from unit help"
|
"hello from unit help"
|
||||||
)
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_optional_source_code() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("struct with optional source")]
|
||||||
|
struct Struct {
|
||||||
|
#[source_code]
|
||||||
|
src: Option<String>,
|
||||||
|
}
|
||||||
|
assert!(Struct { src: None }.source_code().is_none());
|
||||||
|
assert!(Struct {
|
||||||
|
src: Some("".to_string())
|
||||||
|
}
|
||||||
|
.source_code()
|
||||||
|
.is_some());
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
enum Enum {
|
||||||
|
#[error("variant1 with optional source")]
|
||||||
|
Variant1 {
|
||||||
|
#[source_code]
|
||||||
|
src: Option<String>,
|
||||||
|
},
|
||||||
|
#[error("variant2 with optional source")]
|
||||||
|
Variant2 {
|
||||||
|
#[source_code]
|
||||||
|
src: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert!(Enum::Variant1 { src: None }.source_code().is_none());
|
||||||
|
assert!(Enum::Variant1 {
|
||||||
|
src: Some("".to_string())
|
||||||
|
}
|
||||||
|
.source_code()
|
||||||
|
.is_some());
|
||||||
|
assert!(Enum::Variant2 { src: None }.source_code().is_none());
|
||||||
|
assert!(Enum::Variant2 {
|
||||||
|
src: Some("".to_string())
|
||||||
|
}
|
||||||
|
.source_code()
|
||||||
|
.is_some());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,190 @@ fn fmt_report(diag: Report) -> String {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fmt_report_with_settings(
|
||||||
|
diag: Report,
|
||||||
|
with_settings: fn(GraphicalReportHandler) -> GraphicalReportHandler,
|
||||||
|
) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
let handler = with_settings(GraphicalReportHandler::new_themed(
|
||||||
|
GraphicalTheme::unicode_nocolor(),
|
||||||
|
));
|
||||||
|
|
||||||
|
handler.render_report(&mut out, diag.as_ref()).unwrap();
|
||||||
|
|
||||||
|
println!("Error:\n```\n{}\n```", out);
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn word_wrap_options() -> Result<(), MietteError> {
|
||||||
|
// By default, a long word should not break
|
||||||
|
let out =
|
||||||
|
fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| handler);
|
||||||
|
|
||||||
|
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// A long word can break with a smaller width
|
||||||
|
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
|
||||||
|
handler.with_width(10)
|
||||||
|
});
|
||||||
|
let expected = r#" × abcd
|
||||||
|
│ efgh
|
||||||
|
│ ijkl
|
||||||
|
│ mnop
|
||||||
|
│ qrst
|
||||||
|
│ uvwx
|
||||||
|
│ yz
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// Unless, word breaking is disabled
|
||||||
|
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
|
||||||
|
handler.with_width(10).with_break_words(false)
|
||||||
|
});
|
||||||
|
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// Breaks should start at the boundary of each word if possible
|
||||||
|
let out = fmt_report_with_settings(
|
||||||
|
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|
||||||
|
|handler| handler.with_width(10),
|
||||||
|
);
|
||||||
|
let expected = r#" × 12
|
||||||
|
│ 123
|
||||||
|
│ 1234
|
||||||
|
│ 1234
|
||||||
|
│ 5
|
||||||
|
│ 1234
|
||||||
|
│ 56
|
||||||
|
│ 1234
|
||||||
|
│ 567
|
||||||
|
│ 1234
|
||||||
|
│ 5678
|
||||||
|
│ 90
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// But long words should not break if word breaking is disabled
|
||||||
|
let out = fmt_report_with_settings(
|
||||||
|
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|
||||||
|
|handler| handler.with_width(10).with_break_words(false),
|
||||||
|
);
|
||||||
|
let expected = r#" × 12
|
||||||
|
│ 123
|
||||||
|
│ 1234
|
||||||
|
│ 12345
|
||||||
|
│ 123456
|
||||||
|
│ 1234567
|
||||||
|
│ 1234567890
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// Unless, of course, there are hyphens
|
||||||
|
let out = fmt_report_with_settings(
|
||||||
|
Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"),
|
||||||
|
|handler| handler.with_width(10).with_break_words(false),
|
||||||
|
);
|
||||||
|
let expected = r#" × a-b
|
||||||
|
│ a-b-
|
||||||
|
│ c a-
|
||||||
|
│ b-c-
|
||||||
|
│ d a-
|
||||||
|
│ b-c-
|
||||||
|
│ d-e
|
||||||
|
│ a-b-
|
||||||
|
│ c-d-
|
||||||
|
│ e-f
|
||||||
|
│ a-b-
|
||||||
|
│ c-d-
|
||||||
|
│ e-f-
|
||||||
|
│ g a-
|
||||||
|
│ b-c-
|
||||||
|
│ d-e-
|
||||||
|
│ f-g-
|
||||||
|
│ h
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// Which requires an additional opt-out
|
||||||
|
let out = fmt_report_with_settings(
|
||||||
|
Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"),
|
||||||
|
|handler| {
|
||||||
|
handler
|
||||||
|
.with_width(10)
|
||||||
|
.with_break_words(false)
|
||||||
|
.with_word_splitter(textwrap::WordSplitter::NoHyphenation)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let expected = r#" × a-b
|
||||||
|
│ a-b-c
|
||||||
|
│ a-b-c-d
|
||||||
|
│ a-b-c-d-e
|
||||||
|
│ a-b-c-d-e-f
|
||||||
|
│ a-b-c-d-e-f-g
|
||||||
|
│ a-b-c-d-e-f-g-h
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// Or if there are _other_ unicode word boundaries
|
||||||
|
let out = fmt_report_with_settings(
|
||||||
|
Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"),
|
||||||
|
|handler| handler.with_width(10).with_break_words(false),
|
||||||
|
);
|
||||||
|
let expected = r#" × a/b
|
||||||
|
│ a/b/
|
||||||
|
│ c a/
|
||||||
|
│ b/c/
|
||||||
|
│ d a/
|
||||||
|
│ b/c/
|
||||||
|
│ d/e
|
||||||
|
│ a/b/
|
||||||
|
│ c/d/
|
||||||
|
│ e/f
|
||||||
|
│ a/b/
|
||||||
|
│ c/d/
|
||||||
|
│ e/f/
|
||||||
|
│ g a/
|
||||||
|
│ b/c/
|
||||||
|
│ d/e/
|
||||||
|
│ f/g/
|
||||||
|
│ h
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
// Such things require you to opt-in to only breaking on ASCII whitespace
|
||||||
|
let out = fmt_report_with_settings(
|
||||||
|
Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"),
|
||||||
|
|handler| {
|
||||||
|
handler
|
||||||
|
.with_width(10)
|
||||||
|
.with_break_words(false)
|
||||||
|
.with_word_separator(textwrap::WordSeparator::AsciiSpace)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let expected = r#" × a/b
|
||||||
|
│ a/b/c
|
||||||
|
│ a/b/c/d
|
||||||
|
│ a/b/c/d/e
|
||||||
|
│ a/b/c/d/e/f
|
||||||
|
│ a/b/c/d/e/f/g
|
||||||
|
│ a/b/c/d/e/f/g/h
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_source() -> Result<(), MietteError> {
|
fn empty_source() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -67,6 +251,52 @@ fn empty_source() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_spans_multiline() {
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(severity(Error))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("big")]
|
||||||
|
big: SourceSpan,
|
||||||
|
#[label("small")]
|
||||||
|
small: SourceSpan,
|
||||||
|
}
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new(
|
||||||
|
"issue",
|
||||||
|
"\
|
||||||
|
if true {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}",
|
||||||
|
),
|
||||||
|
big: (0, 32).into(),
|
||||||
|
small: (14, 1).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
|
||||||
|
let expected = r#" × oops!
|
||||||
|
╭─[issue:1:1]
|
||||||
|
1 │ ╭─▶ if true {
|
||||||
|
2 │ │╭▶ a
|
||||||
|
· ││ ┬
|
||||||
|
· ││ ╰── small
|
||||||
|
3 │ │ } else {
|
||||||
|
4 │ │ b
|
||||||
|
5 │ ├─▶ }
|
||||||
|
· ╰──── big
|
||||||
|
╰────
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_line_highlight_span_full_line() {
|
fn single_line_highlight_span_full_line() {
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
|
@ -85,9 +315,8 @@ fn single_line_highlight_span_full_line() {
|
||||||
let out = fmt_report(err.into());
|
let out = fmt_report(err.into());
|
||||||
println!("Error: {}", out);
|
println!("Error: {}", out);
|
||||||
|
|
||||||
let expected = r#"
|
let expected = r#" × oops!
|
||||||
× oops!
|
╭─[issue:2:1]
|
||||||
╭─[issue:1:1]
|
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -121,7 +350,7 @@ fn single_line_with_wide_char() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:7]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ 👼🏼text
|
2 │ 👼🏼text
|
||||||
· ───┬──
|
· ───┬──
|
||||||
|
|
@ -160,7 +389,7 @@ fn single_line_with_two_tabs() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -199,7 +428,7 @@ fn single_line_with_tab_in_middle() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:8]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text = text
|
2 │ text = text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -236,7 +465,7 @@ fn single_line_highlight() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -271,7 +500,7 @@ fn external_source() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -344,7 +573,7 @@ fn single_line_highlight_offset_end_of_line() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:1:7]
|
||||||
1 │ source
|
1 │ source
|
||||||
· ▲
|
· ▲
|
||||||
· ╰── this bit here
|
· ╰── this bit here
|
||||||
|
|
@ -380,7 +609,7 @@ fn single_line_highlight_include_end_of_line() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬──
|
· ──┬──
|
||||||
|
|
@ -417,7 +646,7 @@ fn single_line_highlight_include_end_of_line_crlf() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬──
|
· ──┬──
|
||||||
|
|
@ -454,7 +683,7 @@ fn single_line_highlight_with_empty_span() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ▲
|
· ▲
|
||||||
|
|
@ -491,7 +720,7 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ────
|
· ────
|
||||||
|
|
@ -527,7 +756,7 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:1]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -542,6 +771,94 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_label() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("this bit here\nand\nthis\ntoo")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\ntext\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (7, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:1]
|
||||||
|
1 │ source
|
||||||
|
2 │ text
|
||||||
|
· ──┬─
|
||||||
|
· ╰─┤ this bit here
|
||||||
|
· │ and
|
||||||
|
· │ this
|
||||||
|
· │ too
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_multi_line_labels() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label = "x\ny"]
|
||||||
|
highlight1: SourceSpan,
|
||||||
|
#[label = "z\nw"]
|
||||||
|
highlight2: SourceSpan,
|
||||||
|
#[label = "a\nb"]
|
||||||
|
highlight3: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text text text text text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight1: (9, 4).into(),
|
||||||
|
highlight2: (14, 4).into(),
|
||||||
|
highlight3: (24, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:3]
|
||||||
|
1 │ source
|
||||||
|
2 │ text text text text text
|
||||||
|
· ──┬─ ──┬─ ──┬─
|
||||||
|
· │ │ ╰─┤ a
|
||||||
|
· │ │ │ b
|
||||||
|
· │ ╰─┤ z
|
||||||
|
· │ │ w
|
||||||
|
· ╰─┤ x
|
||||||
|
· │ y
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -570,7 +887,7 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text text text text text
|
2 │ text text text text text
|
||||||
· ──┬─ ──┬─ ──┬─
|
· ──┬─ ──┬─ ──┬─
|
||||||
|
|
@ -617,7 +934,7 @@ fn multiple_same_line_highlights_with_tabs_in_middle() -> Result<(), MietteError
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text text text text text
|
2 │ text text text text text
|
||||||
· ──┬─ ──┬─ ──┬─
|
· ──┬─ ──┬─ ──┬─
|
||||||
|
|
@ -656,7 +973,7 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ ╭─▶ text
|
2 │ ╭─▶ text
|
||||||
3 │ ├─▶ here
|
3 │ ├─▶ here
|
||||||
|
|
@ -670,6 +987,43 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_highlight_multiline_label() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label = "these two lines\nare the problem"]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (9, 11).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:3]
|
||||||
|
1 │ source
|
||||||
|
2 │ ╭─▶ text
|
||||||
|
3 │ ├─▶ here
|
||||||
|
· ╰──┤ these two lines
|
||||||
|
· │ are the problem
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -970,7 +1324,7 @@ fn related() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -1032,7 +1386,7 @@ fn related_source_code_propagation() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -1137,7 +1491,7 @@ fn related_severity() -> Result<(), MietteError> {
|
||||||
let expected = r#"oops::my::bad
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
× oops!
|
× oops!
|
||||||
╭─[bad_file.rs:1:1]
|
╭─[bad_file.rs:2:3]
|
||||||
1 │ source
|
1 │ source
|
||||||
2 │ text
|
2 │ text
|
||||||
· ──┬─
|
· ──┬─
|
||||||
|
|
@ -1201,9 +1555,8 @@ fn zero_length_eol_span() {
|
||||||
let out = fmt_report(err.into());
|
let out = fmt_report(err.into());
|
||||||
println!("Error: {}", out);
|
println!("Error: {}", out);
|
||||||
|
|
||||||
let expected = r#"
|
let expected = r#" × oops!
|
||||||
× oops!
|
╭─[issue:2:1]
|
||||||
╭─[issue:1:1]
|
|
||||||
1 │ this is the first line
|
1 │ this is the first line
|
||||||
2 │ this is the second line
|
2 │ this is the second line
|
||||||
· ▲
|
· ▲
|
||||||
|
|
@ -1214,3 +1567,149 @@ fn zero_length_eol_span() {
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn primary_label() {
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label]
|
||||||
|
first_label: SourceSpan,
|
||||||
|
#[label(primary, "nope")]
|
||||||
|
second_label: SourceSpan,
|
||||||
|
}
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("issue", "this is the first line\nthis is the second line"),
|
||||||
|
first_label: (2, 4).into(),
|
||||||
|
second_label: (24, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
|
||||||
|
// line 2 should be the primary, not line 1
|
||||||
|
let expected = r#" × oops!
|
||||||
|
╭─[issue:2:2]
|
||||||
|
1 │ this is the first line
|
||||||
|
· ────
|
||||||
|
2 │ this is the second line
|
||||||
|
· ──┬─
|
||||||
|
· ╰── nope
|
||||||
|
╰────
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_line_with_wide_char_unaligned_span_start() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n 👼🏼text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (10, 5).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:4]
|
||||||
|
1 │ source
|
||||||
|
2 │ 👼🏼text
|
||||||
|
· ──┬─
|
||||||
|
· ╰── this bit here
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_line_with_wide_char_unaligned_span_end() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text 👼🏼\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (9, 6).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:3]
|
||||||
|
1 │ source
|
||||||
|
2 │ text 👼🏼
|
||||||
|
· ───┬───
|
||||||
|
· ╰── this bit here
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_line_with_wide_char_unaligned_span_empty() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n 👼🏼text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (10, 0).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:4]
|
||||||
|
1 │ source
|
||||||
|
2 │ 👼🏼text
|
||||||
|
· ▲
|
||||||
|
· ╰── this bit here
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use miette::{miette, Diagnostic};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_source() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("Bar")]
|
||||||
|
struct Bar;
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("Foo")]
|
||||||
|
struct Foo {
|
||||||
|
#[source]
|
||||||
|
bar: Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
let e = miette!(Foo { bar: Bar });
|
||||||
|
let mut chain = e.chain();
|
||||||
|
|
||||||
|
assert_eq!("Foo", chain.next().unwrap().to_string());
|
||||||
|
assert_eq!("Bar", chain.next().unwrap().to_string());
|
||||||
|
assert!(chain.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_source_boxed() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("Bar")]
|
||||||
|
struct Bar;
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("Foo")]
|
||||||
|
struct Foo {
|
||||||
|
#[source]
|
||||||
|
bar: Box<dyn Diagnostic + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let error = miette!(Foo { bar: Box::new(Bar) });
|
||||||
|
|
||||||
|
let mut chain = error.chain();
|
||||||
|
|
||||||
|
assert_eq!("Foo", chain.next().unwrap().to_string());
|
||||||
|
assert_eq!("Bar", chain.next().unwrap().to_string());
|
||||||
|
assert!(chain.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_source_arc() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("Bar")]
|
||||||
|
struct Bar;
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("Foo")]
|
||||||
|
struct Foo {
|
||||||
|
#[source]
|
||||||
|
bar: Arc<dyn Diagnostic + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let error = miette!(Foo { bar: Arc::new(Bar) });
|
||||||
|
|
||||||
|
let mut chain = error.chain();
|
||||||
|
|
||||||
|
assert_eq!("Foo", chain.next().unwrap().to_string());
|
||||||
|
assert_eq!("Bar", chain.next().unwrap().to_string());
|
||||||
|
assert!(chain.next().is_none());
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,16 @@
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
|
|
||||||
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
#[error("A complex error happened")]
|
||||||
|
struct SourceError {
|
||||||
|
#[source_code]
|
||||||
|
code: String,
|
||||||
|
#[help]
|
||||||
|
help: String,
|
||||||
|
#[label("here")]
|
||||||
|
label: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
#[error("AnErr")]
|
#[error("AnErr")]
|
||||||
struct AnErr;
|
struct AnErr;
|
||||||
|
|
@ -8,7 +19,7 @@ struct AnErr;
|
||||||
#[error("TestError")]
|
#[error("TestError")]
|
||||||
struct TestStructError {
|
struct TestStructError {
|
||||||
#[diagnostic_source]
|
#[diagnostic_source]
|
||||||
asdf_inner_foo: AnErr,
|
asdf_inner_foo: SourceError,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
|
@ -37,7 +48,11 @@ struct TestArcedError(#[diagnostic_source] std::sync::Arc<dyn Diagnostic>);
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diagnostic_source() {
|
fn test_diagnostic_source() {
|
||||||
let error = TestStructError {
|
let error = TestStructError {
|
||||||
asdf_inner_foo: AnErr,
|
asdf_inner_foo: SourceError {
|
||||||
|
code: String::new(),
|
||||||
|
help: String::new(),
|
||||||
|
label: (0, 0),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
assert!(error.diagnostic_source().is_some());
|
assert!(error.diagnostic_source().is_some());
|
||||||
|
|
||||||
|
|
@ -59,3 +74,123 @@ fn test_diagnostic_source() {
|
||||||
let error = TestArcedError(std::sync::Arc::new(AnErr));
|
let error = TestArcedError(std::sync::Arc::new(AnErr));
|
||||||
assert!(error.diagnostic_source().is_some());
|
assert!(error.diagnostic_source().is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[test]
|
||||||
|
fn test_diagnostic_source_pass_extra_info() {
|
||||||
|
let diag = TestBoxedError(Box::new(SourceError {
|
||||||
|
code: String::from("Hello\nWorld!"),
|
||||||
|
help: String::from("Have you tried turning it on and off again?"),
|
||||||
|
label: (1, 4),
|
||||||
|
}));
|
||||||
|
let mut out = String::new();
|
||||||
|
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||||
|
.with_width(80)
|
||||||
|
.with_footer("this is a footer".into())
|
||||||
|
.render_report(&mut out, &diag)
|
||||||
|
.unwrap();
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#" × TestError
|
||||||
|
╰─▶ × A complex error happened
|
||||||
|
╭─[1:2]
|
||||||
|
1 │ Hello
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
2 │ World!
|
||||||
|
╰────
|
||||||
|
help: Have you tried turning it on and off again?
|
||||||
|
|
||||||
|
|
||||||
|
this is a footer
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[test]
|
||||||
|
fn test_diagnostic_source_is_output() {
|
||||||
|
let diag = TestStructError {
|
||||||
|
asdf_inner_foo: SourceError {
|
||||||
|
code: String::from("right here"),
|
||||||
|
help: String::from("That's where the error is!"),
|
||||||
|
label: (6, 4),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut out = String::new();
|
||||||
|
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||||
|
.with_width(80)
|
||||||
|
.render_report(&mut out, &diag)
|
||||||
|
.unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
|
||||||
|
let expected = r#" × TestError
|
||||||
|
╰─▶ × A complex error happened
|
||||||
|
╭────
|
||||||
|
1 │ right here
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
help: That's where the error is!
|
||||||
|
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
#[error("A nested error happened")]
|
||||||
|
struct NestedError {
|
||||||
|
#[source_code]
|
||||||
|
code: String,
|
||||||
|
#[label("here")]
|
||||||
|
label: (usize, usize),
|
||||||
|
#[diagnostic_source]
|
||||||
|
the_other_err: Box<dyn Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[test]
|
||||||
|
fn test_nested_diagnostic_source_is_output() {
|
||||||
|
let inner_error = TestStructError {
|
||||||
|
asdf_inner_foo: SourceError {
|
||||||
|
code: String::from("This is another error"),
|
||||||
|
help: String::from("You should fix this"),
|
||||||
|
label: (3, 4),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let diag = NestedError {
|
||||||
|
code: String::from("right here"),
|
||||||
|
label: (6, 4),
|
||||||
|
the_other_err: Box::new(inner_error),
|
||||||
|
};
|
||||||
|
let mut out = String::new();
|
||||||
|
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||||
|
.with_width(80)
|
||||||
|
.with_footer("Yooo, a footer".to_string())
|
||||||
|
.render_report(&mut out, &diag)
|
||||||
|
.unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
|
||||||
|
let expected = r#" × A nested error happened
|
||||||
|
├─▶ × TestError
|
||||||
|
│
|
||||||
|
╰─▶ × A complex error happened
|
||||||
|
╭────
|
||||||
|
1 │ This is another error
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
help: You should fix this
|
||||||
|
|
||||||
|
╭────
|
||||||
|
1 │ right here
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
|
||||||
|
Yooo, a footer
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -98,7 +97,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -144,7 +142,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -190,7 +187,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -235,7 +231,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -281,7 +276,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -347,7 +341,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -393,7 +386,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -456,7 +448,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -532,7 +523,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -588,7 +578,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -644,7 +633,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -700,7 +688,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -728,7 +715,6 @@ mod json_report_handler {
|
||||||
"related": []
|
"related": []
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -822,7 +808,6 @@ mod json_report_handler {
|
||||||
}]
|
}]
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
@ -920,7 +905,6 @@ mod json_report_handler {
|
||||||
}]
|
}]
|
||||||
}"#
|
}"#
|
||||||
.lines()
|
.lines()
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue