mirror of https://github.com/zkat/miette.git
Compare commits
26 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
e853bbf9bc | |
|
|
b466948965 | |
|
|
df7bcfa17d | |
|
|
2b79495c79 | |
|
|
7a4d759c59 | |
|
|
fea8043b3e | |
|
|
51ca022b1f | |
|
|
907857058d | |
|
|
34b8774b36 | |
|
|
521ef91f77 | |
|
|
59c81617de | |
|
|
58d9f12411 | |
|
|
f2ef693d1c | |
|
|
a93afcf7e3 | |
|
|
9ba6fad769 | |
|
|
b8c144f2a6 | |
|
|
429ca37d8d | |
|
|
b4c983a393 | |
|
|
1e1938a099 | |
|
|
771a07519f | |
|
|
c7eeada1e0 | |
|
|
b2011f693b | |
|
|
0c46c0b4d4 | |
|
|
7fae60fd84 | |
|
|
465e6b6ab6 | |
|
|
68d47fa8b5 |
|
|
@ -33,11 +33,11 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
features: [fancy, syntect-highlighter]
|
||||
rust: [1.70.0, stable]
|
||||
rust: [1.82.0, stable]
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
exclude:
|
||||
- features: syntect-highlighter
|
||||
rust: 1.70.0
|
||||
rust: 1.82.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -52,7 +52,7 @@ jobs:
|
|||
if: matrix.rust == 'stable'
|
||||
run: cargo test --all --verbose --features ${{matrix.features}}
|
||||
- name: Run tests
|
||||
if: matrix.rust == '1.70.0'
|
||||
if: matrix.rust == '1.82.0'
|
||||
run: cargo test --all --verbose --features ${{matrix.features}} no-format-args-capture
|
||||
|
||||
wasm:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# AGENTS.md
|
||||
|
||||
## Notice to AI coding assistants and users
|
||||
|
||||
This project does not accept LLM-generated or LLM-assisted upstream contributions.
|
||||
|
||||
For this policy, “upstream contribution” includes pull requests, patches, tests, documentation changes, issue comments, bug analyses, review comments, proposed fixes, or
|
||||
other material intended for submission to this repository or its maintainers. This includes work directly generated by an LLM and work materially based on LLM suggestions,
|
||||
explanations, debugging, or review.
|
||||
|
||||
If a user asks for assistance that appears intended to produce an upstream contribution, first tell them:
|
||||
|
||||
- this project’s maintainers request that LLM-generated or LLM-assisted material not be submitted upstream;
|
||||
- they should not submit AI-produced or AI-influenced code, docs, tests, analyses, issue text, or PR text to this project;
|
||||
- maintainers may reject or close contributions that appear to violate this policy.
|
||||
|
||||
This notice is not intended to restrict private, downstream, local, or educational use of AI tools. Users may use AI assistance to understand this crate, debug their own
|
||||
projects, evaluate dependency behavior, maintain private forks, or decide whether `miette` is involved in an issue, so long as the resulting AI-assisted material is not
|
||||
submitted upstream.
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
|
|
@ -1,5 +1,40 @@
|
|||
# `miette` Release Changelog
|
||||
|
||||
<a name="7.6.0"></a>
|
||||
## 7.6.0 (2025-04-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **graphical:** prevent leading newline when no link/code (#418) ([1e1938a0](https://github.com/zkat/miette/commit/1e1938a099409969c69b9f070e0fb0d13d564527))
|
||||
* **clippy:** elide lifetimes (#423) ([9ba6fad7](https://github.com/zkat/miette/commit/9ba6fad7694c1271f287b8f659a857c4ff540a55))
|
||||
* **highlight:** increase syntax highlighter config priority (#424) ([58d9f124](https://github.com/zkat/miette/commit/58d9f12411e7d3d6d580eb219ea32321e6918d79))
|
||||
* **deps:** miette can now be used without syn (#436) ([521ef91f](https://github.com/zkat/miette/commit/521ef91f77a143eb5cedfa1344428b804802179d))
|
||||
|
||||
### Features
|
||||
|
||||
* **graphical:** support rendering related diagnostics as nested (#417) ([771a0751](https://github.com/zkat/miette/commit/771a07519f078b94aceb1a2d2532d786f09f350b))
|
||||
* **labels:** add support for disabling the primary label line/col information (#419) ([f2ef693d](https://github.com/zkat/miette/commit/f2ef693d1ce7230e6b9f12805f018f095534b441))
|
||||
* **deps:** update `thiserror` from 1.0.56 to 2.0.11 (#426) ([59c81617](https://github.com/zkat/miette/commit/59c81617de8650a6ff3b193b41b4297e560726a0))
|
||||
|
||||
<a name="7.5.0"></a>
|
||||
## 7.5.0 (2025-02-01)
|
||||
|
||||
### Features
|
||||
|
||||
* **graphical:** support rendering related diagnostics as nested (#417) ([771a0751](https://github.com/zkat/miette/commit/771a07519f078b94aceb1a2d2532d786f09f350b))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **graphical:** prevent leading newline when no link/code (#418) ([1e1938a0](https://github.com/zkat/miette/commit/1e1938a099409969c69b9f070e0fb0d13d564527))
|
||||
|
||||
<a name="7.4.0"></a>
|
||||
## 7.4.0 (2024-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* **graphical:** Inherit source code to causes (#401) ([465e6b6a](https://github.com/zkat/miette/commit/465e6b6ab627f8da34baa5f46441d944fb88186e))
|
||||
* **report:** Implement `WrapError` for `Option` (#409) ([7fae60fd](https://github.com/zkat/miette/commit/7fae60fd8462f95cf3140c6a3b9eb06cb7953405))
|
||||
|
||||
<a name="7.3.0"></a>
|
||||
## 7.3.0 (2024-11-26)
|
||||
|
||||
|
|
|
|||
13
Cargo.toml
13
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "miette"
|
||||
version = "7.3.0"
|
||||
version = "7.6.0"
|
||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||
description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
|
||||
categories = ["rust-patterns"]
|
||||
|
|
@ -9,13 +9,12 @@ documentation = "https://docs.rs/miette"
|
|||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
rust-version = "1.70.0"
|
||||
rust-version = "1.82.0"
|
||||
exclude = ["images/", "tests/", "miette-derive/"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.56"
|
||||
miette-derive = { path = "miette-derive", version = "=7.3.0", optional = true }
|
||||
unicode-width = "0.1.11"
|
||||
miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true }
|
||||
unicode-width = "0.2.0"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
owo-colors = { version = "4.0.0", optional = true }
|
||||
|
|
@ -30,6 +29,7 @@ serde = { version = "1.0.196", features = ["derive"], optional = true }
|
|||
syntect = { version = "5.1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
thiserror = "2.0.11"
|
||||
semver = "1.0.21"
|
||||
|
||||
# Eyre devdeps
|
||||
|
|
@ -37,9 +37,8 @@ futures = { version = "0.3", default-features = false }
|
|||
indenter = "0.3.3"
|
||||
rustversion = "1.0"
|
||||
trybuild = { version = "1.0.89", features = ["diff"] }
|
||||
syn = { version = "2.0.48", features = ["full"] }
|
||||
syn = { version = "2.0.87", features = ["full"] }
|
||||
regex = "1.10"
|
||||
lazy_static = "1.4"
|
||||
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
|
|
|
|||
51
README.md
51
README.md
|
|
@ -51,6 +51,7 @@ diagnostic error code: ruget::api::bad_json
|
|||
- [... handler options](#-handler-options)
|
||||
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
- [... syntax highlighting](#-syntax-highlighting)
|
||||
- [... primary label](#-primary-label)
|
||||
- [... collection of labels](#-collection-of-labels)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
- [License](#license)
|
||||
|
|
@ -100,7 +101,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`!
|
||||
*/
|
||||
use miette::{Diagnostic, SourceSpan};
|
||||
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
|
|
@ -127,7 +128,7 @@ Use this `Result` type (or its expanded version) as the return type
|
|||
throughout your app (but NOT your libraries! Those should always return
|
||||
concrete types!).
|
||||
*/
|
||||
use miette::{NamedSource, Result};
|
||||
use miette::Result;
|
||||
fn this_fails() -> Result<()> {
|
||||
// You can use plain strings as a `Source`, or anything that implements
|
||||
// the one-method `Source` trait.
|
||||
|
|
@ -210,6 +211,17 @@ pub enum MyLibError {
|
|||
// Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||
#[diagnostic(transparent)]
|
||||
AnotherError(#[from] AnotherError),
|
||||
|
||||
/// Forward the diagnostic to a particular field.
|
||||
#[error("other error")]
|
||||
#[diagnostic(forward(the_actual_diagnostic))]
|
||||
EvenMoreData {
|
||||
unrelated_field_1: String,
|
||||
unrelated_field_2: usize,
|
||||
|
||||
#[source]
|
||||
the_actual_diagnostic: AnotherError,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Diagnostic, Debug)]
|
||||
|
|
@ -266,7 +278,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
|
||||
// my_app/lib/my_internal_file.rs
|
||||
use miette::{miette, Result};
|
||||
|
|
@ -689,6 +701,37 @@ trait to [`MietteHandlerOpts`] by calling the
|
|||
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||
method. See the [`highlighters`] module docs for more details.
|
||||
|
||||
#### ... primary label
|
||||
|
||||
You can use the `primary` parameter to `label` to indicate that the label
|
||||
is the primary label.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyError {
|
||||
#[label(primary, "main issue")]
|
||||
primary_span: SourceSpan,
|
||||
|
||||
#[label("other label")]
|
||||
other_span: SourceSpan,
|
||||
}
|
||||
```
|
||||
|
||||
The `primary` parameter can be used at most once:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
struct MyError {
|
||||
#[label(primary, "main issue")]
|
||||
primary_span: SourceSpan,
|
||||
|
||||
#[label(primary, "other label")] // Error: Cannot have more than one primary label.
|
||||
other_span: SourceSpan,
|
||||
}
|
||||
```
|
||||
|
||||
#### ... collection of labels
|
||||
|
||||
When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||
|
|
@ -742,7 +785,7 @@ println!("{:?}", report.with_source_code("About something or another or yet anot
|
|||
|
||||
### MSRV
|
||||
|
||||
This crate requires rustc 1.70.0 or later.
|
||||
This crate requires rustc 1.82.0 or later.
|
||||
|
||||
### Acknowledgements
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
msrv = "1.70.0"
|
||||
msrv = "1.82.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "miette-derive"
|
||||
version = "7.3.0"
|
||||
version = "7.6.0"
|
||||
authors = ["Kat Marchán <kzm@zkat.tech>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
|
|
@ -11,6 +11,6 @@ repository = "https://github.com/zkat/miette"
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.78"
|
||||
proc-macro2 = "1.0.83"
|
||||
quote = "1.0.35"
|
||||
syn = "2.0.48"
|
||||
syn = "2.0.87"
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ impl Forward {
|
|||
Self::#variant { #field_name, .. } => #field_name.#method_call,
|
||||
},
|
||||
Forward::Unnamed(index) => {
|
||||
let underscores: Vec<_> = core::iter::repeat(quote! { _, }).take(*index).collect();
|
||||
let underscores: Vec<_> = std::iter::repeat_n(quote! { _, }, *index).collect();
|
||||
let unnamed = format_ident!("unnamed");
|
||||
quote! {
|
||||
Self::#variant ( #(#underscores)* #unnamed, .. ) => #unnamed.#method_call,
|
||||
|
|
|
|||
|
|
@ -1,40 +1,6 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
};
|
||||
|
||||
pub(crate) enum MemberOrString {
|
||||
Member(syn::Member),
|
||||
String(syn::LitStr),
|
||||
}
|
||||
|
||||
impl ToTokens for MemberOrString {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
use MemberOrString::*;
|
||||
match self {
|
||||
Member(member) => member.to_tokens(tokens),
|
||||
String(string) => string.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MemberOrString {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) {
|
||||
Ok(MemberOrString::Member(input.parse()?))
|
||||
} else if lookahead.peek(syn::LitStr) {
|
||||
Ok(MemberOrString::String(input.parse()?))
|
||||
} else {
|
||||
Err(syn::Error::new(
|
||||
input.span(),
|
||||
"Expected a string or a field reference.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
use quote::{format_ident, quote};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ impl<'a> ErrorKind<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
||||
impl std::fmt::Debug for ErrorKind<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||
|
|
@ -83,7 +83,7 @@ impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for ErrorKind<'a> {
|
||||
impl std::fmt::Display for ErrorKind<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||
|
|
|
|||
88
src/error.rs
88
src/error.rs
|
|
@ -1,25 +1,51 @@
|
|||
use std::{fmt, io};
|
||||
|
||||
use thiserror::Error;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
io,
|
||||
};
|
||||
|
||||
use crate::Diagnostic;
|
||||
|
||||
/**
|
||||
Error enum for miette. Used by certain operations in the protocol.
|
||||
*/
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum MietteError {
|
||||
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
||||
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
||||
#[error(transparent)]
|
||||
IoError(#[from] io::Error),
|
||||
IoError(io::Error),
|
||||
|
||||
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
||||
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
||||
#[error("The given offset is outside the bounds of its Source")]
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
impl Display for MietteError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
MietteError::IoError(error) => write!(f, "{error}"),
|
||||
MietteError::OutOfBounds => {
|
||||
write!(f, "The given offset is outside the bounds of its Source")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MietteError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
MietteError::IoError(error) => error.source(),
|
||||
MietteError::OutOfBounds => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for MietteError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for MietteError {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
match self {
|
||||
|
|
@ -49,3 +75,51 @@ impl Diagnostic for MietteError {
|
|||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use std::{error::Error, io::ErrorKind};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TestError(pub(crate) io::Error);
|
||||
|
||||
impl Display for TestError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "testing, testing...")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TestError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn io_error() {
|
||||
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
|
||||
let outer_error = TestError(inner_error);
|
||||
let io_error = io::Error::new(ErrorKind::Other, outer_error);
|
||||
|
||||
let miette_error = MietteError::from(io_error);
|
||||
|
||||
assert_eq!(miette_error.to_string(), "testing, testing...");
|
||||
assert_eq!(
|
||||
miette_error.source().unwrap().to_string(),
|
||||
"halt and catch fire"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_bounds() {
|
||||
let miette_error = MietteError::OutOfBounds;
|
||||
|
||||
assert_eq!(
|
||||
miette_error.to_string(),
|
||||
"The given offset is outside the bounds of its Source"
|
||||
);
|
||||
assert_eq!(miette_error.source().map(ToString::to_string), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,44 @@ mod ext {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> WrapErr<T, std::convert::Infallible> for Option<T> {
|
||||
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
match self {
|
||||
Some(t) => Ok(t),
|
||||
None => Err(Report::from(crate::eyreish::wrapper::DisplayError(msg))),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
match self {
|
||||
Some(t) => Ok(t),
|
||||
None => Err(Report::from(crate::eyreish::wrapper::DisplayError(msg()))),
|
||||
}
|
||||
}
|
||||
|
||||
fn context<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.wrap_err(msg)
|
||||
}
|
||||
|
||||
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
self.wrap_err_with(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> WrapErr<T, E> for Result<T, E>
|
||||
where
|
||||
E: ext::Diag + Send + Sync + 'static,
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ impl Report {
|
|||
/// The root cause is the last error in the iterator produced by
|
||||
/// [`chain()`](Report::chain).
|
||||
pub fn root_cause(&self) -> &(dyn StdError + 'static) {
|
||||
self.chain().last().unwrap()
|
||||
self.chain().next_back().unwrap()
|
||||
}
|
||||
|
||||
/// Returns true if `E` is the type held by this error object.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,24 @@
|
|||
use thiserror::Error;
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use crate::{Diagnostic, Report};
|
||||
|
||||
/// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for
|
||||
/// Errors. This is intended to be paired with [`IntoDiagnostic`].
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
|
||||
|
||||
impl Display for DiagnosticError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let msg = &self.0;
|
||||
write!(f, "{msg}")
|
||||
}
|
||||
}
|
||||
impl Error for DiagnosticError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
self.0.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for DiagnosticError {}
|
||||
|
||||
/**
|
||||
|
|
@ -31,3 +43,26 @@ impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for R
|
|||
self.map_err(|e| DiagnosticError(Box::new(e)).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::{self, ErrorKind};
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::error::tests::TestError;
|
||||
|
||||
#[test]
|
||||
fn diagnostic_error() {
|
||||
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
|
||||
let outer_error: Result<(), _> = Err(TestError(inner_error));
|
||||
|
||||
let diagnostic_error = outer_error.into_diagnostic().unwrap_err();
|
||||
|
||||
assert_eq!(diagnostic_error.to_string(), "testing, testing...");
|
||||
assert_eq!(
|
||||
diagnostic_error.source().unwrap().to_string(),
|
||||
"halt and catch fire"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,11 +172,7 @@ pub trait ReportHandler: core::any::Any + Send + Sync {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn debug(
|
||||
&self,
|
||||
error: &(dyn Diagnostic),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result;
|
||||
fn debug(&self, error: &dyn Diagnostic, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
|
||||
|
||||
/// Override for the `Display` format
|
||||
fn display(
|
||||
|
|
|
|||
|
|
@ -69,9 +69,9 @@ where
|
|||
lifetime: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Copy for Ref<'a, T> where T: ?Sized {}
|
||||
impl<T> Copy for Ref<'_, T> where T: ?Sized {}
|
||||
|
||||
impl<'a, T> Clone for Ref<'a, T>
|
||||
impl<T> Clone for Ref<'_, T>
|
||||
where
|
||||
T: ?Sized,
|
||||
{
|
||||
|
|
@ -132,9 +132,9 @@ where
|
|||
lifetime: PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Copy for Mut<'a, T> where T: ?Sized {}
|
||||
impl<T> Copy for Mut<'_, T> where T: ?Sized {}
|
||||
|
||||
impl<'a, T> Clone for Mut<'a, T>
|
||||
impl<T> Clone for Mut<'_, T>
|
||||
where
|
||||
T: ?Sized,
|
||||
{
|
||||
|
|
@ -173,7 +173,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Mut<'a, T> {
|
||||
impl<T> Mut<'_, T> {
|
||||
pub(crate) unsafe fn read(self) -> T {
|
||||
self.ptr.as_ptr().read()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,30 @@ use crate::{Diagnostic, LabeledSpan, Report, SourceCode};
|
|||
|
||||
use crate as miette;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct DisplayError<M>(pub(crate) M);
|
||||
|
||||
impl<M> Debug for DisplayError<M>
|
||||
where
|
||||
M: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Display for DisplayError<M>
|
||||
where
|
||||
M: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
|
||||
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct MessageError<M>(pub(crate) M);
|
||||
|
||||
|
|
|
|||
210
src/handler.rs
210
src/handler.rs
|
|
@ -1,5 +1,3 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::highlighters::Highlighter;
|
||||
use crate::highlighters::MietteHighlighter;
|
||||
use crate::protocol::Diagnostic;
|
||||
|
|
@ -9,6 +7,8 @@ use crate::NarratableReportHandler;
|
|||
use crate::ReportHandler;
|
||||
use crate::ThemeCharacters;
|
||||
use crate::ThemeStyles;
|
||||
use cfg_if::cfg_if;
|
||||
use std::fmt;
|
||||
|
||||
/// Settings to control the color format used for graphical rendering.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||
|
|
@ -57,6 +57,7 @@ pub struct MietteHandlerOpts {
|
|||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||
pub(crate) highlighter: Option<MietteHighlighter>,
|
||||
pub(crate) show_related_as_nested: Option<bool>,
|
||||
}
|
||||
|
||||
impl MietteHandlerOpts {
|
||||
|
|
@ -98,7 +99,9 @@ impl MietteHandlerOpts {
|
|||
/// Setting this option will not force color output. In all cases, the
|
||||
/// current color configuration via
|
||||
/// [`color()`](MietteHandlerOpts::color()) takes precedence over
|
||||
/// highlighter configuration.
|
||||
/// highlighter configuration. However, this option does take precedence over
|
||||
/// [`rgb_colors()`](MietteHandlerOpts::rgb_colors()) (meaning syntax highlighting will be
|
||||
/// enabled regardless of the value of [`MietteHandlerOpts::rgb_colors`]).
|
||||
pub fn with_syntax_highlighting(
|
||||
mut self,
|
||||
highlighter: impl Highlighter + Send + Sync + 'static,
|
||||
|
|
@ -167,6 +170,18 @@ impl MietteHandlerOpts {
|
|||
self
|
||||
}
|
||||
|
||||
/// Show related errors as siblings.
|
||||
pub fn show_related_errors_as_siblings(mut self) -> Self {
|
||||
self.show_related_as_nested = Some(false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Show related errors as nested errors.
|
||||
pub fn show_related_errors_as_nested(mut self) -> Self {
|
||||
self.show_related_as_nested = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// If true, colors will be used during graphical rendering, regardless
|
||||
/// of whether or not the terminal supports them.
|
||||
///
|
||||
|
|
@ -190,6 +205,8 @@ impl MietteHandlerOpts {
|
|||
/// first place. That is handled by the [`MietteHandlerOpts::color`]
|
||||
/// setting. If colors are not being used, the value of `rgb_colors` has
|
||||
/// no effect.
|
||||
///
|
||||
/// It also does not control colors when a syntax highlighter is in use.
|
||||
pub fn rgb_colors(mut self, color: RgbColors) -> Self {
|
||||
self.rgb_colors = color;
|
||||
self
|
||||
|
|
@ -279,31 +296,13 @@ impl MietteHandlerOpts {
|
|||
} else {
|
||||
ThemeStyles::none()
|
||||
};
|
||||
#[cfg(not(feature = "syntect-highlighter"))]
|
||||
let highlighter = self.highlighter.unwrap_or_else(MietteHighlighter::nocolor);
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
let highlighter = if self.color == Some(false) {
|
||||
MietteHighlighter::nocolor()
|
||||
} else if self.color == Some(true) || syscall::supports_color() {
|
||||
match self.highlighter {
|
||||
Some(highlighter) => highlighter,
|
||||
None => match self.rgb_colors {
|
||||
// Because the syntect highlighter currently only supports 24-bit truecolor,
|
||||
// respect RgbColor::Never by disabling the highlighter.
|
||||
// TODO: In the future, find a way to convert the RGB syntect theme
|
||||
// into an ANSI color theme.
|
||||
RgbColors::Never => MietteHighlighter::nocolor(),
|
||||
_ => MietteHighlighter::syntect_truecolor(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
MietteHighlighter::nocolor()
|
||||
};
|
||||
let highlighter_opt =
|
||||
HighlighterOption::select(self.color, self.highlighter, syscall::supports_color());
|
||||
let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
|
||||
let mut handler = GraphicalReportHandler::new_themed(theme)
|
||||
.with_width(width)
|
||||
.with_links(linkify);
|
||||
handler.highlighter = highlighter;
|
||||
handler.highlighter = highlighter_opt.into();
|
||||
if let Some(with_cause_chain) = self.with_cause_chain {
|
||||
if with_cause_chain {
|
||||
handler = handler.with_cause_chain();
|
||||
|
|
@ -332,6 +331,9 @@ impl MietteHandlerOpts {
|
|||
if let Some(s) = self.word_splitter {
|
||||
handler = handler.with_word_splitter(s)
|
||||
}
|
||||
if let Some(b) = self.show_related_as_nested {
|
||||
handler = handler.with_show_related_as_nested(b)
|
||||
}
|
||||
|
||||
MietteHandler {
|
||||
inner: Box::new(handler),
|
||||
|
|
@ -400,7 +402,7 @@ impl Default for MietteHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for MietteHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
@ -409,6 +411,58 @@ impl ReportHandler for MietteHandler {
|
|||
}
|
||||
}
|
||||
|
||||
enum HighlighterOption {
|
||||
Disable,
|
||||
EnableCustom(MietteHighlighter),
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
EnableSyntect,
|
||||
}
|
||||
|
||||
impl HighlighterOption {
|
||||
fn select(
|
||||
color: Option<bool>,
|
||||
highlighter: Option<MietteHighlighter>,
|
||||
supports_color: bool,
|
||||
) -> HighlighterOption {
|
||||
if color == Some(false) || (color.is_none() && !supports_color) {
|
||||
return HighlighterOption::Disable;
|
||||
}
|
||||
highlighter
|
||||
.map(HighlighterOption::EnableCustom)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This is manually implemented so that it's clearer what's going on with
|
||||
// the conditional compilation — clippy isn't picking up the `cfg` stuff here
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for HighlighterOption {
|
||||
fn default() -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "syntect-highlighter")] {
|
||||
// Because the syntect highlighter currently only supports 24-bit truecolor,
|
||||
// it supersedes and ignores the `rgb_colors` config.
|
||||
// TODO: In the future, if we find a way to convert the RGB syntect theme
|
||||
// into an ANSI color theme, we can take `rgb_colors` into account.
|
||||
HighlighterOption::EnableSyntect
|
||||
} else {
|
||||
HighlighterOption::Disable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HighlighterOption> for MietteHighlighter {
|
||||
fn from(opt: HighlighterOption) -> Self {
|
||||
match opt {
|
||||
HighlighterOption::Disable => MietteHighlighter::nocolor(),
|
||||
HighlighterOption::EnableCustom(highlighter) => highlighter,
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
HighlighterOption::EnableSyntect => MietteHighlighter::syntect_truecolor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod syscall {
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
|
|
@ -434,7 +488,6 @@ mod syscall {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
#[inline]
|
||||
pub(super) fn supports_color() -> bool {
|
||||
cfg_if! {
|
||||
|
|
@ -468,3 +521,108 @@ mod syscall {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::highlighters::BlankHighlighter;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
#[test]
|
||||
fn test_highlighter_option() {
|
||||
// Syntax highlighting is enabled depending on several variables:
|
||||
// - The `color` config
|
||||
// - The `highlighter` config
|
||||
// - Whether the `syntect-highlighter` feature is enabled
|
||||
// - Whether the terminal supports color
|
||||
//
|
||||
// This test asserts the expected highlighter depending on combinations of those variables.
|
||||
|
||||
macro_rules! assert_highlighter_opt {
|
||||
(opts = $opts:expr, supports_color = $sup_color:literal, expected = $expected:pat $(,)?) => {
|
||||
assert_highlighter_opt!(
|
||||
opts = $opts,
|
||||
supports_color = $sup_color,
|
||||
expected_with_syntect = $expected,
|
||||
expected_without_syntect = $expected,
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
opts = $opts:expr,
|
||||
supports_color = $sup_color:literal,
|
||||
expected_with_syntect = $expected_with:pat,
|
||||
expected_without_syntect = $expected_without:pat $(,)?
|
||||
) => {{
|
||||
let highlighter_opt =
|
||||
HighlighterOption::select($opts.color, $opts.highlighter, $sup_color);
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "syntect-highlighter")] {
|
||||
assert!(matches!(highlighter_opt, $expected_with));
|
||||
} else {
|
||||
assert!(matches!(highlighter_opt, $expected_without));
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// When color is explicitly disabled, highlighting is also always disabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().color(false),
|
||||
supports_color = true,
|
||||
expected = HighlighterOption::Disable,
|
||||
);
|
||||
|
||||
// When color is unset and the terminal doesn't support color, highlighting is disabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new(),
|
||||
supports_color = false,
|
||||
expected = HighlighterOption::Disable,
|
||||
);
|
||||
|
||||
// With explicit or implicit color support, highlighting is automatically enabled when
|
||||
// `syntect-highlighter` is enabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().color(true),
|
||||
supports_color = false,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new(),
|
||||
supports_color = true,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
|
||||
// With explicit or implicit color support, if custom highlighting is set, it's enabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new()
|
||||
.color(true)
|
||||
.with_syntax_highlighting(BlankHighlighter),
|
||||
supports_color = false,
|
||||
expected = HighlighterOption::EnableCustom(_),
|
||||
);
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().with_syntax_highlighting(BlankHighlighter),
|
||||
supports_color = true,
|
||||
expected = HighlighterOption::EnableCustom(_),
|
||||
);
|
||||
|
||||
// Setting `RgbColors::Never` has no effect when syntax highlighting is enabled.
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new()
|
||||
.color(true)
|
||||
.rgb_colors(RgbColors::Never),
|
||||
supports_color = false,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
assert_highlighter_opt!(
|
||||
opts = MietteHandlerOpts::new().rgb_colors(RgbColors::Never),
|
||||
supports_color = true,
|
||||
expected_with_syntect = HighlighterOption::EnableSyntect,
|
||||
expected_without_syntect = HighlighterOption::Disable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ impl DebugReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
let mut diag = f.debug_struct("Diagnostic");
|
||||
diag.field("message", &format!("{}", diagnostic));
|
||||
|
|
@ -61,7 +61,7 @@ impl DebugReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for DebugReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,12 @@ pub struct GraphicalReportHandler {
|
|||
pub(crate) with_cause_chain: bool,
|
||||
pub(crate) wrap_lines: bool,
|
||||
pub(crate) break_words: bool,
|
||||
pub(crate) with_primary_span_start: bool,
|
||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||
pub(crate) highlighter: MietteHighlighter,
|
||||
pub(crate) link_display_text: Option<String>,
|
||||
pub(crate) show_related_as_nested: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -60,10 +62,12 @@ impl GraphicalReportHandler {
|
|||
with_cause_chain: true,
|
||||
wrap_lines: true,
|
||||
break_words: true,
|
||||
with_primary_span_start: true,
|
||||
word_separator: None,
|
||||
word_splitter: None,
|
||||
highlighter: MietteHighlighter::default(),
|
||||
link_display_text: None,
|
||||
show_related_as_nested: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,11 +82,13 @@ impl GraphicalReportHandler {
|
|||
tab_width: 4,
|
||||
wrap_lines: true,
|
||||
with_cause_chain: true,
|
||||
with_primary_span_start: true,
|
||||
break_words: true,
|
||||
word_separator: None,
|
||||
word_splitter: None,
|
||||
highlighter: MietteHighlighter::default(),
|
||||
link_display_text: None,
|
||||
show_related_as_nested: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +122,20 @@ impl GraphicalReportHandler {
|
|||
self
|
||||
}
|
||||
|
||||
/// Include the line and column for the the start of the primary span when the
|
||||
/// snippet extends multiple lines
|
||||
pub fn with_primary_span_start(mut self) -> Self {
|
||||
self.with_primary_span_start = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Do not include the line and column for the the start of the primary span
|
||||
/// when the snippet extends multiple lines
|
||||
pub fn without_primary_span_start(mut self) -> Self {
|
||||
self.with_primary_span_start = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to include [`Diagnostic::url()`] in the output.
|
||||
///
|
||||
/// Disabling this is not recommended, but can be useful for more easily
|
||||
|
|
@ -177,6 +197,12 @@ impl GraphicalReportHandler {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets whether to render related errors as nested errors.
|
||||
pub fn with_show_related_as_nested(mut self, show_related_as_nested: bool) -> Self {
|
||||
self.show_related_as_nested = show_related_as_nested;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable syntax highlighting for source code snippets, using the given
|
||||
/// [`Highlighter`]. See the [highlighters](crate::highlighters) crate
|
||||
/// for more details.
|
||||
|
|
@ -216,11 +242,20 @@ impl GraphicalReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
self.render_header(f, diagnostic)?;
|
||||
self.render_causes(f, diagnostic)?;
|
||||
let src = diagnostic.source_code();
|
||||
self.render_report_inner(f, diagnostic, diagnostic.source_code())
|
||||
}
|
||||
|
||||
fn render_report_inner(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let src = diagnostic.source_code().or(parent_src);
|
||||
self.render_header(f, diagnostic, false)?;
|
||||
self.render_causes(f, diagnostic, src)?;
|
||||
self.render_snippets(f, diagnostic, src)?;
|
||||
self.render_footer(f, diagnostic)?;
|
||||
self.render_related(f, diagnostic, src)?;
|
||||
|
|
@ -243,13 +278,19 @@ impl GraphicalReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_header(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &dyn Diagnostic,
|
||||
is_nested: bool,
|
||||
) -> fmt::Result {
|
||||
let severity_style = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => self.theme.styles.error,
|
||||
Some(Severity::Warning) => self.theme.styles.warning,
|
||||
Some(Severity::Advice) => self.theme.styles.advice,
|
||||
};
|
||||
let mut header = String::new();
|
||||
let mut need_newline = is_nested;
|
||||
if self.links == LinkStyle::Link && diagnostic.url().is_some() {
|
||||
let url = diagnostic.url().unwrap(); // safe
|
||||
let code = if let Some(code) = diagnostic.code() {
|
||||
|
|
@ -266,6 +307,7 @@ impl GraphicalReportHandler {
|
|||
);
|
||||
write!(header, "{}", link)?;
|
||||
writeln!(f, "{}", header)?;
|
||||
need_newline = true;
|
||||
} else if let Some(code) = diagnostic.code() {
|
||||
write!(header, "{}", code.style(severity_style),)?;
|
||||
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
|
||||
|
|
@ -273,12 +315,22 @@ impl GraphicalReportHandler {
|
|||
write!(header, " ({})", url.style(self.theme.styles.link))?;
|
||||
}
|
||||
writeln!(f, "{}", header)?;
|
||||
need_newline = true;
|
||||
}
|
||||
if need_newline {
|
||||
writeln!(f)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_causes(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let src = diagnostic.source_code().or(parent_src);
|
||||
|
||||
let (severity_style, severity_icon) = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => (self.theme.styles.error, &self.theme.characters.error),
|
||||
Some(Severity::Warning) => (self.theme.styles.warning, &self.theme.characters.warning),
|
||||
|
|
@ -356,7 +408,7 @@ impl GraphicalReportHandler {
|
|||
inner_renderer.with_cause_chain = false;
|
||||
// Since everything from here on is indented, shrink the virtual terminal
|
||||
inner_renderer.termwidth -= rest_indent.width();
|
||||
inner_renderer.render_report(&mut inner, diag)?;
|
||||
inner_renderer.render_report_inner(&mut inner, diag, src)?;
|
||||
|
||||
// If there was no header, remove the leading newline
|
||||
let inner = inner.trim_start_matches('\n');
|
||||
|
|
@ -372,7 +424,7 @@ impl GraphicalReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
if let Some(help) = diagnostic.help() {
|
||||
let width = self.termwidth.saturating_sub(2);
|
||||
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
|
||||
|
|
@ -395,26 +447,86 @@ impl GraphicalReportHandler {
|
|||
fn render_related(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let src = diagnostic.source_code().or(parent_src);
|
||||
|
||||
if let Some(related) = diagnostic.related() {
|
||||
let severity_style = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => self.theme.styles.error,
|
||||
Some(Severity::Warning) => self.theme.styles.warning,
|
||||
Some(Severity::Advice) => self.theme.styles.advice,
|
||||
};
|
||||
|
||||
let mut inner_renderer = self.clone();
|
||||
// Re-enable the printing of nested cause chains for related errors
|
||||
inner_renderer.with_cause_chain = true;
|
||||
for rel in related {
|
||||
writeln!(f)?;
|
||||
match rel.severity() {
|
||||
Some(Severity::Error) | None => write!(f, "Error: ")?,
|
||||
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||
};
|
||||
inner_renderer.render_header(f, rel)?;
|
||||
inner_renderer.render_causes(f, rel)?;
|
||||
let src = rel.source_code().or(parent_src);
|
||||
inner_renderer.render_snippets(f, rel, src)?;
|
||||
inner_renderer.render_footer(f, rel)?;
|
||||
inner_renderer.render_related(f, rel, src)?;
|
||||
if self.show_related_as_nested {
|
||||
let width = self.termwidth.saturating_sub(2);
|
||||
let mut related = related.peekable();
|
||||
while let Some(rel) = related.next() {
|
||||
let is_last = related.peek().is_none();
|
||||
let char = if !is_last {
|
||||
self.theme.characters.lcross
|
||||
} else {
|
||||
self.theme.characters.lbot
|
||||
};
|
||||
let initial_indent = format!(
|
||||
" {}{}{} ",
|
||||
char, self.theme.characters.hbar, self.theme.characters.rarrow
|
||||
)
|
||||
.style(severity_style)
|
||||
.to_string();
|
||||
let rest_indent = format!(
|
||||
" {} ",
|
||||
if is_last {
|
||||
' '
|
||||
} else {
|
||||
self.theme.characters.vbar
|
||||
}
|
||||
)
|
||||
.style(severity_style)
|
||||
.to_string();
|
||||
|
||||
let mut opts = textwrap::Options::new(width)
|
||||
.initial_indent(&initial_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);
|
||||
}
|
||||
|
||||
let mut inner = String::new();
|
||||
|
||||
let mut inner_renderer = self.clone();
|
||||
inner_renderer.footer = None;
|
||||
inner_renderer.with_cause_chain = false;
|
||||
inner_renderer.termwidth -= rest_indent.width();
|
||||
inner_renderer.render_report_inner(&mut inner, rel, src)?;
|
||||
|
||||
// If there was no header, remove the leading newline
|
||||
let inner = inner.trim_matches('\n');
|
||||
writeln!(f, "{}", self.wrap(inner, opts))?;
|
||||
}
|
||||
} else {
|
||||
for rel in related {
|
||||
writeln!(f)?;
|
||||
match rel.severity() {
|
||||
Some(Severity::Error) | None => write!(f, "Error: ")?,
|
||||
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||
};
|
||||
inner_renderer.render_header(f, rel, true)?;
|
||||
let src = rel.source_code().or(parent_src);
|
||||
inner_renderer.render_causes(f, rel, src)?;
|
||||
inner_renderer.render_snippets(f, rel, src)?;
|
||||
inner_renderer.render_footer(f, rel)?;
|
||||
inner_renderer.render_related(f, rel, src)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -423,7 +535,7 @@ impl GraphicalReportHandler {
|
|||
fn render_snippets(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
opt_source: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
let source = match opt_source {
|
||||
|
|
@ -569,26 +681,34 @@ impl GraphicalReportHandler {
|
|||
};
|
||||
|
||||
if let Some(source_name) = primary_contents.name() {
|
||||
writeln!(
|
||||
f,
|
||||
"[{}]",
|
||||
format_args!(
|
||||
"{}:{}:{}",
|
||||
source_name,
|
||||
primary_contents.line() + 1,
|
||||
primary_contents.column() + 1
|
||||
)
|
||||
.style(self.theme.styles.link)
|
||||
)?;
|
||||
} else if lines.len() <= 1 {
|
||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
||||
} else {
|
||||
if self.with_primary_span_start {
|
||||
writeln!(
|
||||
f,
|
||||
"[{}]",
|
||||
format_args!(
|
||||
"{}:{}:{}",
|
||||
source_name,
|
||||
primary_contents.line() + 1,
|
||||
primary_contents.column() + 1
|
||||
)
|
||||
.style(self.theme.styles.link)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"[{}]",
|
||||
format_args!("{}", source_name,).style(self.theme.styles.link)
|
||||
)?;
|
||||
}
|
||||
} else if self.with_primary_span_start && lines.len() > 1 {
|
||||
writeln!(
|
||||
f,
|
||||
"[{}:{}]",
|
||||
primary_contents.line() + 1,
|
||||
primary_contents.column() + 1
|
||||
)?;
|
||||
} else {
|
||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
||||
}
|
||||
|
||||
// Now it's time for the fun part--actually rendering everything!
|
||||
|
|
@ -1241,7 +1361,7 @@ impl GraphicalReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for GraphicalReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl JSONReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
self._render_report(f, diagnostic, None)
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ impl JSONReportHandler {
|
|||
fn _render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
|
||||
|
|
@ -154,7 +154,7 @@ impl JSONReportHandler {
|
|||
fn render_snippets(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
source: &dyn SourceCode,
|
||||
) -> fmt::Result {
|
||||
if let Some(mut labels) = diagnostic.labels() {
|
||||
|
|
@ -170,7 +170,7 @@ impl JSONReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for JSONReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.render_report(f, diagnostic)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ impl NarratableReportHandler {
|
|||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
) -> fmt::Result {
|
||||
self.render_header(f, diagnostic)?;
|
||||
if self.with_cause_chain {
|
||||
|
|
@ -85,7 +85,7 @@ impl NarratableReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
writeln!(f, "{}", diagnostic)?;
|
||||
let severity = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => "error",
|
||||
|
|
@ -96,7 +96,7 @@ impl NarratableReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
if let Some(cause_iter) = diagnostic
|
||||
.diagnostic_source()
|
||||
.map(DiagnosticChain::from_diagnostic)
|
||||
|
|
@ -110,7 +110,7 @@ impl NarratableReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &dyn Diagnostic) -> fmt::Result {
|
||||
if let Some(help) = diagnostic.help() {
|
||||
writeln!(f, "diagnostic help: {}", help)?;
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ impl NarratableReportHandler {
|
|||
fn render_related(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
if let Some(related) = diagnostic.related() {
|
||||
|
|
@ -152,7 +152,7 @@ impl NarratableReportHandler {
|
|||
fn render_snippets(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
diagnostic: &dyn Diagnostic,
|
||||
source_code: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
if let Some(source) = source_code {
|
||||
|
|
@ -344,7 +344,7 @@ impl NarratableReportHandler {
|
|||
}
|
||||
|
||||
impl ReportHandler for NarratableReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,12 @@ impl SyntectHighlighter {
|
|||
}
|
||||
}
|
||||
// finally, attempt to guess syntax based on first line
|
||||
return self.syntax_set.find_syntax_by_first_line(
|
||||
self.syntax_set.find_syntax_by_first_line(
|
||||
std::str::from_utf8(contents.data())
|
||||
.ok()?
|
||||
.split('\n')
|
||||
.next()?,
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ pub(crate) struct SyntectHighlighterState<'h> {
|
|||
use_bg_color: bool,
|
||||
}
|
||||
|
||||
impl<'h> HighlighterState for SyntectHighlighterState<'h> {
|
||||
impl HighlighterState for SyntectHighlighterState<'_> {
|
||||
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
|
||||
if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) {
|
||||
let use_bg_color = self.use_bg_color;
|
||||
|
|
@ -125,7 +125,7 @@ impl<'h> HighlighterState for SyntectHighlighterState<'h> {
|
|||
line,
|
||||
&self.highlighter,
|
||||
)
|
||||
.map(|(style, str)| (convert_style(style, use_bg_color).style(str)))
|
||||
.map(|(style, str)| convert_style(style, use_bg_color).style(str))
|
||||
.collect()
|
||||
} else {
|
||||
vec![Style::default().style(line)]
|
||||
|
|
|
|||
51
src/lib.rs
51
src/lib.rs
|
|
@ -51,6 +51,7 @@
|
|||
//! - [... handler options](#-handler-options)
|
||||
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
//! - [... syntax highlighting](#-syntax-highlighting)
|
||||
//! - [... primary label](#-primary-label)
|
||||
//! - [... collection of labels](#-collection-of-labels)
|
||||
//! - [Acknowledgements](#acknowledgements)
|
||||
//! - [License](#license)
|
||||
|
|
@ -100,7 +101,7 @@
|
|||
//!
|
||||
//! `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;
|
||||
//!
|
||||
//! #[derive(Error, Debug, Diagnostic)]
|
||||
|
|
@ -127,7 +128,7 @@
|
|||
//! throughout your app (but NOT your libraries! Those should always return
|
||||
//! concrete types!).
|
||||
//! */
|
||||
//! use miette::{NamedSource, Result};
|
||||
//! use miette::Result;
|
||||
//! fn this_fails() -> Result<()> {
|
||||
//! // You can use plain strings as a `Source`, or anything that implements
|
||||
//! // the one-method `Source` trait.
|
||||
|
|
@ -210,6 +211,17 @@
|
|||
//! // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
|
||||
//! #[diagnostic(transparent)]
|
||||
//! AnotherError(#[from] AnotherError),
|
||||
//!
|
||||
//! /// Forward the diagnostic to a particular field.
|
||||
//! #[error("other error")]
|
||||
//! #[diagnostic(forward(the_actual_diagnostic))]
|
||||
//! EvenMoreData {
|
||||
//! unrelated_field_1: String,
|
||||
//! unrelated_field_2: usize,
|
||||
//!
|
||||
//! #[source]
|
||||
//! the_actual_diagnostic: AnotherError,
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Error, Diagnostic, Debug)]
|
||||
|
|
@ -266,7 +278,7 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! To construct your own simple adhoc error use the [miette!] macro:
|
||||
//! To construct your own simple adhoc error use the [`miette!`] macro:
|
||||
//! ```rust
|
||||
//! // my_app/lib/my_internal_file.rs
|
||||
//! use miette::{miette, Result};
|
||||
|
|
@ -690,6 +702,37 @@
|
|||
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||
//! method. See the [`highlighters`] module docs for more details.
|
||||
//!
|
||||
//! ### ... primary label
|
||||
//!
|
||||
//! You can use the `primary` parameter to `label` to indicate that the label
|
||||
//! is the primary label.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("oops!")]
|
||||
//! struct MyError {
|
||||
//! #[label(primary, "main issue")]
|
||||
//! primary_span: SourceSpan,
|
||||
//!
|
||||
//! #[label("other label")]
|
||||
//! other_span: SourceSpan,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The `primary` parameter can be used at most once:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("oops!")]
|
||||
//! struct MyError {
|
||||
//! #[label(primary, "main issue")]
|
||||
//! primary_span: SourceSpan,
|
||||
//!
|
||||
//! #[label(primary, "other label")] // Error: Cannot have more than one primary label.
|
||||
//! other_span: SourceSpan,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### ... collection of labels
|
||||
//!
|
||||
//! When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||
|
|
@ -743,7 +786,7 @@
|
|||
//!
|
||||
//! ## MSRV
|
||||
//!
|
||||
//! This crate requires rustc 1.70.0 or later.
|
||||
//! This crate requires rustc 1.82.0 or later.
|
||||
//!
|
||||
//! ## Acknowledgements
|
||||
//!
|
||||
|
|
|
|||
58
src/panic.rs
58
src/panic.rs
|
|
@ -1,7 +1,8 @@
|
|||
use backtrace::Backtrace;
|
||||
use thiserror::Error;
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use crate::{self as miette, Context, Diagnostic, Result};
|
||||
use backtrace::Backtrace;
|
||||
|
||||
use crate::{Context, Diagnostic, Result};
|
||||
|
||||
/// Tells miette to render panics using its rendering engine.
|
||||
pub fn set_panic_hook() {
|
||||
|
|
@ -25,11 +26,27 @@ pub fn set_panic_hook() {
|
|||
}));
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("{0}{}", Panic::backtrace())]
|
||||
#[diagnostic(help("set the `RUST_BACKTRACE=1` environment variable to display a backtrace."))]
|
||||
#[derive(Debug)]
|
||||
struct Panic(String);
|
||||
|
||||
impl Display for Panic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let msg = &self.0;
|
||||
let panic = Panic::backtrace();
|
||||
write!(f, "{msg}{panic}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Panic {}
|
||||
|
||||
impl Diagnostic for Panic {
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
Some(Box::new(
|
||||
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Panic {
|
||||
fn backtrace() -> String {
|
||||
use std::fmt::Write;
|
||||
|
|
@ -84,3 +101,32 @@ impl Panic {
|
|||
"".into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::error::Error;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn panic() {
|
||||
let panic = Panic("ruh roh raggy".to_owned());
|
||||
|
||||
assert_eq!(panic.to_string(), "ruh roh raggy");
|
||||
assert!(panic.source().is_none());
|
||||
assert!(panic.code().is_none());
|
||||
assert!(panic.severity().is_none());
|
||||
assert_eq!(
|
||||
panic.help().map(|h| h.to_string()),
|
||||
Some(
|
||||
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace."
|
||||
.to_owned()
|
||||
)
|
||||
);
|
||||
assert!(panic.url().is_none());
|
||||
assert!(panic.source_code().is_none());
|
||||
assert!(panic.labels().is_none());
|
||||
assert!(panic.related().is_none());
|
||||
assert!(panic.diagnostic_source().is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use std::{
|
|||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::MietteError;
|
||||
use crate::{DiagnosticError, MietteError};
|
||||
|
||||
/// Adds rich metadata to your Error that can be used by
|
||||
/// [`Report`](crate::Report) to print really nice and human-friendly error
|
||||
|
|
@ -134,7 +134,7 @@ impl From<&str> for Box<dyn Diagnostic> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
|
||||
impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
|
||||
fn from(s: &str) -> Self {
|
||||
From::from(String::from(s))
|
||||
}
|
||||
|
|
@ -174,18 +174,7 @@ impl From<String> for Box<dyn Diagnostic + Send + Sync> {
|
|||
|
||||
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
|
||||
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
|
||||
#[derive(thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
|
||||
impl fmt::Debug for BoxedDiagnostic {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for BoxedDiagnostic {}
|
||||
|
||||
Box::new(BoxedDiagnostic(s))
|
||||
Box::new(DiagnosticError(s))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ impl SourceCode for [u8] {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'src> SourceCode for &'src [u8] {
|
||||
impl SourceCode for &[u8] {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &SourceSpan,
|
||||
|
|
@ -143,7 +143,7 @@ impl SourceCode for str {
|
|||
}
|
||||
|
||||
/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
|
||||
impl<'s> SourceCode for &'s str {
|
||||
impl SourceCode for &str {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &SourceSpan,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#![cfg(feature = "fancy-no-backtrace")]
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
||||
use regex::Regex;
|
||||
use std::ffi::OsString;
|
||||
|
|
@ -69,9 +68,7 @@ impl Drop for EnvVarGuard<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref COLOR_ENV_VARS: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
static COLOR_ENV_VARS: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Assert the color format used by a handler with different levels of terminal
|
||||
/// support.
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
let out =
|
||||
fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| handler);
|
||||
|
||||
let expected = "\n × abcdefghijklmnopqrstuvwxyz\n".to_string();
|
||||
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// A long word can break with a smaller width
|
||||
|
|
@ -75,14 +75,14 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ uvwx
|
||||
│ yz
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
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 = "\n × abcdefghijklmnopqrstuvwxyz\n".to_string();
|
||||
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Breaks should start at the boundary of each word if possible
|
||||
|
|
@ -104,7 +104,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ 5678
|
||||
│ 90
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// But long words should not break if word breaking is disabled
|
||||
|
|
@ -121,7 +121,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ 1234567
|
||||
│ 1234567890
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Unless, of course, there are hyphens
|
||||
|
|
@ -149,7 +149,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ f-g-
|
||||
│ h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Which requires an additional opt-out
|
||||
|
|
@ -171,7 +171,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ a-b-c-d-e-f-g
|
||||
│ a-b-c-d-e-f-g-h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Or if there are _other_ unicode word boundaries
|
||||
|
|
@ -199,7 +199,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ f/g/
|
||||
│ h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Such things require you to opt-in to only breaking on ASCII whitespace
|
||||
|
|
@ -221,7 +221,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
│ a/b/c/d/e/f/g
|
||||
│ a/b/c/d/e/f/g/h
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -245,7 +245,7 @@ fn wrap_option() -> Result<(), MietteError> {
|
|||
│ pqr stu
|
||||
│ vwx yz
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Unless, wrapping is disabled
|
||||
|
|
@ -254,7 +254,7 @@ fn wrap_option() -> Result<(), MietteError> {
|
|||
|handler| handler.with_width(15).with_wrap_lines(false),
|
||||
);
|
||||
let expected =
|
||||
"\n × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string();
|
||||
" × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string();
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Then, user-defined new lines should be preserved wrapping is disabled
|
||||
|
|
@ -267,7 +267,7 @@ fn wrap_option() -> Result<(), MietteError> {
|
|||
│ abc def ghi jkl mno pqr stu vwx yz
|
||||
│ abc def ghi jkl mno pqr stu vwx yz
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -485,8 +485,7 @@ if true {
|
|||
· ╰──── big
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -517,8 +516,7 @@ fn single_line_highlight_span_full_line() {
|
|||
· ╰── This bit here
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -1781,8 +1779,7 @@ fn zero_length_eol_span() {
|
|||
· ╰── This bit here
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -1817,8 +1814,7 @@ fn primary_label() {
|
|||
· ╰── nope
|
||||
╰────
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
.trim_start_matches('\n');
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -1968,7 +1964,8 @@ fn syntax_highlighter() {
|
|||
· ╰── this is a label
|
||||
3 │ }
|
||||
╰────
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
assert!(out.contains("\u{1b}[38;2;180;142;173m"));
|
||||
assert_eq!(expected, strip_ansi_escapes::strip_str(out))
|
||||
}
|
||||
|
|
@ -2028,6 +2025,7 @@ fn syntax_highlighter_on_real_file() {
|
|||
l2 = line,
|
||||
l3 = line + 1
|
||||
);
|
||||
let expected = expected.trim_start_matches('\n');
|
||||
assert!(out.contains("\u{1b}[38;2;180;142;173m"));
|
||||
assert_eq!(expected, strip_ansi_escapes::strip_str(out));
|
||||
}
|
||||
|
|
@ -2510,3 +2508,63 @@ fn after_invalid_unicode() -> Result<(), MietteError> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_include_primary_span_start() {
|
||||
#[derive(Debug, Clone, Diagnostic, Error)]
|
||||
#[error("decoding error")]
|
||||
#[diagnostic(code(decode_err))]
|
||||
struct E {
|
||||
#[label("valid data here")]
|
||||
src: SourceSpan,
|
||||
}
|
||||
|
||||
let invalid_source: &[u8] = b"malformed\nh\xf0\x93\x8aXYZ";
|
||||
|
||||
let (x_index, _) = invalid_source
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, &x)| x == b'X')
|
||||
.unwrap();
|
||||
|
||||
// make err pointing at the X
|
||||
let err = E {
|
||||
src: SourceSpan::from((x_index, 1)),
|
||||
};
|
||||
|
||||
let result = fmt_report_with_settings(
|
||||
Report::new(err.clone()).with_source_code(invalid_source),
|
||||
|handler| handler,
|
||||
);
|
||||
|
||||
let expected = "decode_err
|
||||
|
||||
× decoding error
|
||||
╭─[2:5]
|
||||
1 │ malformed
|
||||
2 │ h<EFBFBD>XYZ
|
||||
· ┬
|
||||
· ╰── valid data here
|
||||
╰────
|
||||
";
|
||||
|
||||
assert_eq!(expected, result);
|
||||
|
||||
let result = fmt_report_with_settings(
|
||||
Report::new(err).with_source_code(invalid_source),
|
||||
|handler| handler.without_primary_span_start(),
|
||||
);
|
||||
|
||||
let expected = "decode_err
|
||||
|
||||
× decoding error
|
||||
╭────
|
||||
1 │ malformed
|
||||
2 │ h<EFBFBD>XYZ
|
||||
· ┬
|
||||
· ╰── valid data here
|
||||
╰────
|
||||
";
|
||||
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ fn test_diagnostic_source_pass_extra_info() {
|
|||
|
||||
this is a footer
|
||||
"#
|
||||
.to_string();
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +150,8 @@ fn test_diagnostic_source_is_output() {
|
|||
╰────
|
||||
help: That's where the error is!
|
||||
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
|
@ -208,7 +210,8 @@ fn test_nested_diagnostic_source_is_output() {
|
|||
╰────
|
||||
|
||||
Yooo, a footer
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
|
@ -295,7 +298,120 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
|
|||
╰────
|
||||
|
||||
Yooo, a footer
|
||||
"#;
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn test_display_related_errors_as_nested() {
|
||||
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 first_error = NestedError {
|
||||
code: String::from("right here"),
|
||||
label: (6, 4),
|
||||
the_other_err: Box::new(inner_error),
|
||||
};
|
||||
let second_error = SourceError {
|
||||
code: String::from("You're actually a mess"),
|
||||
help: String::from("Get a grip..."),
|
||||
label: (3, 4),
|
||||
};
|
||||
let diag = MultiError {
|
||||
related_errs: vec![
|
||||
Box::new(MultiError {
|
||||
related_errs: vec![Box::new(first_error), Box::new(AnErr)],
|
||||
}),
|
||||
Box::new(second_error),
|
||||
],
|
||||
};
|
||||
|
||||
let mut out = String::new();
|
||||
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||
.with_width(80)
|
||||
.with_show_related_as_nested(true)
|
||||
.render_report(&mut out, &diag)
|
||||
.unwrap();
|
||||
println!("{}", out);
|
||||
|
||||
let expected = r#"
|
||||
× A multi-error happened
|
||||
├─▶ × A multi-error happened
|
||||
│ ├─▶ × A nested error happened
|
||||
│ │ ╭────
|
||||
│ │ 1 │ right here
|
||||
│ │ · ──┬─
|
||||
│ │ · ╰── here
|
||||
│ │ ╰────
|
||||
│ ╰─▶ × AnErr
|
||||
╰─▶ × A complex error happened
|
||||
╭────
|
||||
1 │ You're actually a mess
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
help: Get a grip...
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("A case1 error happened")]
|
||||
enum NestedEnumError {
|
||||
Case1 {
|
||||
#[source_code]
|
||||
code: String,
|
||||
#[diagnostic_source]
|
||||
the_other_err: Case1Inner,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("I am the inner error")]
|
||||
struct Case1Inner {
|
||||
#[label("inner-label")]
|
||||
label: (usize, usize),
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn source_is_inherited_to_causes() {
|
||||
let diag = NestedEnumError::Case1 {
|
||||
code: String::from("this is the parent source"),
|
||||
the_other_err: Case1Inner { label: (8, 3) },
|
||||
};
|
||||
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 case1 error happened
|
||||
╰─▶ × I am the inner error
|
||||
╭────
|
||||
1 │ this is the parent source
|
||||
· ─┬─
|
||||
· ╰── inner-label
|
||||
╰────
|
||||
|
||||
|
||||
Yooo, a footer
|
||||
"#
|
||||
.trim_start_matches('\n');
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue